[RESOLVIDO]TextWatcher - gerando looping infinito

8 respostas
Artur_Bernardo

Não tem como colocar todo código aqui, porque envolve várias classes.

Tenho uma classe Mask, onde há um TextWatcher. Ele é quem coloco a máscara no telefone.
Na Activity, pego o campo editText e dou um .addTextChangedListener(), com isso ele seta a máscara com base no que eu criei na classe Mask.

Por trás, na hierarquia de classes, eu chamo um .setText, que é quem “dispara” o TextWatcher. (ao menos é assim que eu entendi).

O caso é que no meu código não há looping infinito… debugei e me parece que o looping está na classe textView, que é nativa.

A minha chamada setText não está em looping infinito.

Log.d("EditText", "EditText"); ( (EditText) view ).setText( value );

dentro da classe Mask (que chama o TextWatcher) também não,

Log.d("Looping?", "Looping?"); this.mask = "(##)#####-####";

entre as duas classes, está a textView. Só sobra ela de opção.

Outro detalhe é que o bug só ocorre quando estou recriando a Activity. Entro nela, tudo ok… se eu desligo o tela do aparelho e volto, ai ferra.

Eu vi que este problema não é só meu. Seria necessário retirar o textListner “editText.removeTextChangedListener(this)”.
Só que eu fiz isso e não funcionou.

Será que o contextoestá errado?
Adicionei assim:

fone2.addTextChangedListener(Mask.phone(fone2));
Removi assim:

(obs: deve ter ficado confuso, mas não sei se tinha como expicar melhor)

8 Respostas

Marky.Vasconcelos

Pode postar a classe Mask e TextWatcher?

Artur_Bernardo
Marky.Vasconcelos:
Pode postar a classe Mask e TextWatcher?

Li sobre, e vi que ocorre as vezes este problema. As soluções que achei não funcionaram para mim.

Aqui é o método que está dentro da classe mask. Ele que fica sendo chamado em looping infinito. Quem chama ele? Acho que é a classe textView, nativa do Android.

public static TextWatcher phone( final EditText ediTxt )
    {
        return new TextWatcher()
        {
            String mask = "(##)####-####";
            boolean isUpdating;
            String oldText = "";

            @Override
            public void onTextChanged( CharSequence s, int start, int before, int count )
            {
                String str = Mask.unmask( s.toString() );
                String mascara = "";

                if ( str.startsWith( "119" ) )
                {
                    this.mask = "(##)#####-####";
                }
                else
                {
                    this.mask = "(##)####-####";
                }

                if ( this.isUpdating )
                {
                    this.oldText = str;
                    this.isUpdating = false;
                    return;
                }

                int i = 0;

                for ( char m : this.mask.toCharArray() )
                {
                    if ( m != '#' && str.length() > this.oldText.length() )
                    {
                        mascara += m;
                        continue;
                    }
                    try
                    {
                        mascara += str.charAt( i );
                    }
                    catch ( Exception e )
                    {
                        break;
                    }
                    i++;
                }

                this.isUpdating = true;

                if ( mascara != null )
                {
                    ediTxt.setText( mascara );
                    ediTxt.setSelection( mascara.length() );
                }
            }

            @Override
            public void beforeTextChanged( CharSequence s, int start, int count, int after )
            {
            }

            @Override
            public void afterTextChanged( Editable s )
            {
            }
        };
    }
}

Isso aqui é chamado no ONRESUME da minha Activity. Acredito que o problema esteja quando a maskara do telefone é chamada, na linhas de 25 até 29

/**
	 * Define a configuração inicial para as informações de endereços
	 */
	public void inicializaEnderecos() {
		this.uf = (Spinner) this.findViewById(R.id.estado);
		this.cidade = (AutoCompleteTextView) this.findViewById(R.id.cidade);
		this.cidade.setOnFocusChangeListener(new CidadeFocusListener());

		// faz abrir com todas cidades quando clicado pela primeira vez
		this.cidade.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				ClientehostActivity.this.preencheCidades();
				ClientehostActivity.this.cidade.showDropDown();
			}
		});

		// adiciona máscara ao cep
		EditText cep = ((EditText) this.findViewById(R.id.cep));
		cep.addTextChangedListener(Mask.insert("#####-###", cep));
		cep.setOnFocusChangeListener(new CepOnFocusChangeListener(
				SimpleValidate.VALIDATE_CEP));

		// máscara de telefone
		EditText fone1 = ((EditText) this.findViewById(R.id.fone1));
		fone1.addTextChangedListener(Mask.phone(fone1));
		
		EditText fone2 = ((EditText) this.findViewById(R.id.fone2));
		fone2.addTextChangedListener(Mask.phone(fone2));

		// preeenche os estados/uf
		this.uf.setAdapter(this.gtmUf.getAdapter(this.gtmUf.listAll(), true));

		// quando a uf é modificada
		this.uf.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> arg0, View arg1,
					int arg2, long arg3) {
				ClientehostActivity.this.preencheCidades();

				// limpa a cidade selecionada após troca de estado, caso não
				// esteja limpo
				if (!ClientehostActivity.this.cidade.getText().equals("")) {
					if (!ClientehostActivity.this.validaCidade()) {
						ClientehostActivity.this.cidade.setText("");
					}
				}
			}

			@Override
			public void onNothingSelected(AdapterView<?> arg0) {
				Toast.makeText(ClientehostActivity.this,
						"É necessário selecionar algum estado.",
						Toast.LENGTH_LONG).show();
			}
		});

		// ativa validações básicas para os campos
		GtmTipoDeEndereco tiposDeEndereco = new GtmTipoDeEndereco(this);
		((Spinner) this.findViewById(R.id.tipoDeEndereco))
				.setAdapter(tiposDeEndereco.getAdapter(
						tiposDeEndereco.listAll(), true));
		((EditText) this.findViewById(R.id.endereco))
				.setOnFocusChangeListener(new SimpleValidate(
						SimpleValidate.VALIDATE_REQUIRED));
		((EditText) this.findViewById(R.id.numero))
				.setOnFocusChangeListener(new SimpleValidate(
						SimpleValidate.VALIDATE_REQUIRED));
		((EditText) this.findViewById(R.id.bairro))
				.setOnFocusChangeListener(new SimpleValidate(
						SimpleValidate.VALIDATE_REQUIRED));
		((EditText) this.findViewById(R.id.fone1))
				.setOnFocusChangeListener(new SimpleValidate(
						SimpleValidate.VALIDATE_REQUIRED));
		((EditText) this.findViewById(R.id.emailNFE))
				.setOnFocusChangeListener(new SimpleValidate(
						SimpleValidate.VALIDATE_EMAIL));

		this.siglaPadrao();
	}
Artur_Bernardo

Se quiser ver a classe mask inteira, eu posto sem problema. Mas pareceu desnecessário, ia apenas “floodar” o tópico.

Artur_Bernardo

Fui na https://code.google.com/p/android/ e achei alguns problemas semelhantes, mas ainda sem solução.
Achei muita gente nos “stackoverflows” da vida reportando erros semelhantes. As soluções não funcionaram.
Quase sempre, é remover o textChangeListener, mas eu não consigo “enquadrar” isso no meu código, já que o textWatcher está em uma classe (mask), e a view Fone1 está em outra.

Marky.Vasconcelos

Tenho quase certeza que chamar setText vai disparar onTextChanged, nesse caso, ao mudar o texto dentro da classe de Mask vai acabar resultando em ser invocado novamente.

Artur_Bernardo

é mais ou menos isso que está ocorrendo… mas o código já estava feito assim antes de eu entrar na empresa, e está difícil “desacoplar”.
Ao menos é um bug conhecido e aceito ¬¬

Artur_Bernardo

Realmente, era o setText… eu confiei na implementação, e culpei o Android. hehehehe
Estava envolvido d+ no problema e perdi a clareza de raciocínio.
Quando vi o problema, depois de um bom tempo debugando, fui pelo caminho “errado” para corrigir.

Uma mini POG corrigiu a POG maior.

if ( mascara != null ) { if (Mask.unmask( mascara ).length() < (mascara.length()-1)) { ediTxt.setText( mascara ); ediTxt.setSelection( mascara.length() ); } }

Marky.Vasconcelos

Bem tenho que admitir que a solução as vezes é essa mesma.

Por exemplo ao sobreescrever onLayout de uma View, as vezes ao chamar child.layout(l, t, r, b) vai acabar invocando invalidate que irá disparar o processo de layout novamente. Nesse caso, uma flag “LAYOUT_PHASE=true” ajuda a resolver.

Criado 14 de agosto de 2013
Ultima resposta 19 de ago. de 2013
Respostas 8
Participantes 2