[RESOLVIDO]TextWatcher - gerando looping infinito

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)

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.

[code] 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 )
        {
        }
    };
}

}[/code]

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

[code]/**
* 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();
}[/code]

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

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.

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.

é 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 ¬¬

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() ); } }

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.