0

Tengo un problema que no puedo replicar. Estoy utilizando un ViewPager para mostrar un formulario con varios tabs, cada tab es un fragment. El usuario completa cada uno de los tabs y presiona un botón "Enviar". En ese momento se recorre cada uno de los fragments para validar los datos de la siguiente manera:

private boolean validarFormularios() {
    boolean errores = false;
    for (int i = 0; i < adapter.getCount(); i++) {
        Fragment fragment = adapter.getItem(i);

        int canterror = 0;
        if (fragment instanceof FragmentForm) {
            canterror = ((FragmentForm) fragment).validarFormulario();
            if (canterror > 0) errores = true;
        }
        setBadgeTab(canterror, i);
    }
    return errores;
}

Y dentro de cada fragment valida algo como lo siguiente:

if (tvCalle.length() == 0) {
        validado++;
        tilCalle.setError(getResources().getString(R.string.error_campo_ob));
    } else {
        if (tvCalle.length() > tilCalle.getCounterMaxLength()) {
            validado++;
            tilCalle.setError(getResources().getString(R.string.error_campo_excede));
        } else {
            tilCalle.setError(null);
        }
    }
    return validado;

Todo esto funciona correctamente cada vez que lo pruebo. Sin embargo, estoy utilizando Firebase y por medio de la herramienta Crashlytics puedo ver que ocacionalmente hay usuarios que les genera un NullpointerException la línea mencionada anteriormente:

 if (tvCalle.length() == 0) 

El error que recibo es el siguiente:

> Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.widget.TextView.length()' on a null object reference
       at com.edensa.wem.fragment.pnt.InfoSumFragment.validarCalle(InfoSumFragment.java:200)
       at com.edensa.wem.fragment.pnt.InfoSumFragment.validarFormulario(InfoSumFragment.java:176)
       at com.edensa.wem.activity.FormActivity.validarFormularios(FormActivity.java:262)

tvCalle es inicializado desde el onCreateView de uno de los fragments de la siguiente manera:

TextView tvCalle;  
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {  
 View rootview = inflater.inflate(R.layout.fragment_info_sum, container, false);   
    tvCalle = rootview.findViewById(R.id.tvNombre);
}

Como dije, siempre que lo pruebo funciona bien, pero hay una situación que no puedo replicar en la que sale por nullpointer.

Cabe destacar que es una aplicación productiva que la usan no menos de 100 personas durante todo el día. La gran mayoría de las veces no tienen problemas, sin embargo unas 20 0 30 veces al día recibo el error planteado anteriormente. Con esto quiero decir que no hay un error lógico en el código. Sospecho que ante un determinado evento que no puedo deducir, el objeto pierde su valor o se realiza un "detach" del fragment.

Si me pueden ayudar con este problema, se los voy a agradecer.

Saludos.

Leftraru
  • 1
  • 3
  • Pues no sé de donde puede venir el problema... pero no se solucionaría con cambiar el `if` para incluir un nullcheck? `if (tvCalle != null && tvCalle.length() == 0) ` – Benito-B Jan 14 '21 at 19:26
  • Hola, deberías agregar como se inicializa tvCalle y definir si se llama desde una Activity o un Fragmet o si se llama desde varias partes. – Jorgesys Jan 14 '21 at 19:26
  • @Benito-B Gracias por responder. Si, podría chequear si tvCalle y no saldría por nullpointer, pero tendría que pedirle al usuario que complete nuevamente el formulario. Tener en cuenta que esto le lleva bastante tiempo, por eso lo que busco es encontrar el problema de raíz – Leftraru Jan 14 '21 at 19:36
  • @Jorgesys Hola, gracias por la respuesta. Agregue a la pregunta lo que me pediste. Saludos – Leftraru Jan 14 '21 at 19:41
  • ¿Responde esto a tu pregunta? [¿Cuál es la solución a todos los errores NullPointerException presentes, pasados y futuros?](https://es.stackoverflow.com/questions/42977/cu%c3%a1l-es-la-soluci%c3%b3n-a-todos-los-errores-nullpointerexception-presentes-pasados) – Mauricio Contreras Jan 14 '21 at 21:41
  • Hola @MauricioContreras. El problema no es el nullpointer en sí, conozco los motivos por lo que sale ese error y como dijo Benito-B basta con chequear si la variable es nula para que no salga por el error. El problema es que siempre que lo pruebo anda correctamente y es una aplicación productiva que usan no menos de 100 personas durante todo el día y funciona correctamente, salvo unas 20 o 30 veces en las que falla con dicho error. – Leftraru Jan 15 '21 at 15:18

2 Answers2

0

Comentas que tu TextView es inicializado dentro de onCreateView() de uno de tus fragments, esto es correcto pero la definición de tu TextView debería ser de forma general, es decir fuera de onCreateView() para que el "scope" de tu vista se realice dentro de toda la clase.

  private TextView tvCalle;

  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
       View rootview = inflater.inflate(R.layout.fragment_info_sum, container, false);
       //TextView tvCalle;    
       tvCalle = rootview.findViewById(R.id.tvNombre);
       ...
       ...
       ... 
    }
Jorgesys
  • 103,630
  • 13
  • 52
  • 124
  • Es correcto @Jorgesys, asi lo tengo hecho. Me equivoqué al editar la pregunta. Ahora la volví a editar para que se entienda. Igualmente recordar que siempre que lo pruebo anda, y le anda bien a todo el mundo la gran mayoría de las veces. Así que no hay un error lógico en el código. El error entiendo es porque en algún momento pirde el objeto en memoria, o hace un "detach" del fragment. Pero no entiendo cuándo pasa esto y es por eso que no lo puedo replicar. – Leftraru Jan 15 '21 at 15:07
0

Efectivamente mi problema era que el fragment era destruido y regenerado. Independientemente de la manera en la que los usuarios lo lograban hacer, el problema era que al reconstruirse el fragment perdía su estado al referenciarlo desde el adapter.

Por lo que cambié el código:

private boolean validarFormularios() {
    boolean errores = false;
    for (int i = 0; i < adapter.getCount(); i++) {
        Fragment fragment = adapter.getItem(i);

        int canterror = 0;
        if (fragment instanceof FragmentForm) {
            canterror = ((FragmentForm) fragment).validarFormulario();
            if (canterror > 0) errores = true;
        }
        setBadgeTab(canterror, i);
    }
    return errores; }

Por el siguiente:

private boolean validarFormularios() {
        boolean errores = false;
        int tabIndex = 0;
        List<Fragment> fragments = getSupportFragmentManager().getFragments();
        for (Fragment fragment : fragments) {
            int canterror = 0;
            if (fragment instanceof FragmentForm) {
                canterror = ((FragmentForm) fragment).validarFormulario();
                if (canterror > 0) errores = true;
                setBadgeTab(canterror, tabIndex++);
            }
        }
        return errores;
    }

Es decir, cambié la manera en que obtenía los fragments desde la clase Activity. En lugar de hacer adapter.getItem(i) lo que hago es getSupportFragmentManager().getFragments(); y luego iterar la lista de fragments.

Espero que a alguien le pueda servir.

Leftraru
  • 1
  • 3