Blog14 of GrrrNeko BASE

このコーナーではデベロッパー向けに、私がハマってしまったポイントとその解決法を書いていきます。

2012-10-8

android: alertdialog の挙動がおかしい

症状:

android: 異なるスコープ(ブロック、スレッド)間での値の受け渡しで解決したかに思われたが、ちょっと早計、糠喜びだった… orz

 たしかにそれはそれで機能している。しかし、目的は「兄弟ブロック」(同じ親スコープ内での別スコープ)に値を渡してやること、または子から親へ値を送ること。

 以下のようなコードを書くと、またぞろ例の「 final でなければ参照できないが final では代入できない」という呪縛の輪廻が始まり、変数 a を handleMessage から取り出すことができない。

public class MainActivity extends Activity {

    static int a = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        final Handler handler = new Handler() {
    	public void handleMessage(Message msg) {
    	    Bundle bd = msg.getData();
    	    a = bd.getInt("var");
    	}
        };
        
        final AlertDialog.Builder adb = new AlertDialog.Builder(this);
        adb.setMessage("SELECT")
            .setPositiveButton("YES",
		new DialogInterface.OnClickListener() {
	    public void onClick(DialogInterface dialog, int which) {
	        Message msg = new Message();
	        Bundle bd = new Bundle();
	        bd.putInt("var", 100);
	        msg.setData(bd);
	        handler.sendMessage(msg);
	    }
            })
            .setNegativeButton("NO",
		new DialogInterface.OnClickListener() {
	    public void onClick(DialogInterface dialog, int which) {
	        Message msg = new Message();
	        Bundle bd = new Bundle();
	        bd.putInt("var", 200);
	        msg.setData(bd);
	        handler.sendMessage(msg);
	    }
            })
            .show();

        TextView tv = (TextView) findViewById(R.id.textview1);
        tv.setText("Result: " + a);
        
        }
}

 ところが、いろいろいじっているうちに、コンパイルが通るようになった。おそらくいろんなところに final とか static とか付けまくっているうちにいい組み合わせになったんだろう。

 おv。と思って走らせていると、初期値のゼロ…。やっぱり渡されてない (T_T|||) と思って、自棄になってもっかいやってみると、ん?。値が出たぞ!、よしもっかい、出た。けどなんか変。

 ワンサイクル遅れて出力される!すなわち…

 一回目:何を押そうが、初期値の 0。
 二回目:一回目に押した値。
 三回目:二回目に押した値…

 異なるスコープ(ブロック、スレッド)間での値の受け渡しができない。あらため、alertdialog の挙動がおかしい。まさに、いっこく堂状態。

解決法:

 いろいろインターネットを徘徊しているうちに、alertdialog についても理解が深まり、上記のコードと同内容のものを、ごくごくシンプルに書くことができるようにはなった。それがこちら。

public class MainActivity extends Activity {

    static int a = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
	        
        final AlertDialog.Builder adb = new AlertDialog.Builder(this);
        adb.setMessage("SELECT")
	.setPositiveButton("YES", new DialogInterface.OnClickListener() {
	    public void onClick(DialogInterface dialog, int which) {
	    a = 100;
	}
        })
	.setNegativeButton("NO", new DialogInterface.OnClickListener() {
	    public void onClick(DialogInterface dialog, int which) {
	    a = 200;
	}
        })
	.show();
        
        TextView tv = (TextView) findViewById(R.id.textview1);
        tv.setText("Result: " + a);
		
    }
}

 しかしながら、上記ではいまだ「いっこく堂」状態。

 原因はどうやら android の場合、クリックを待たずにその先を処理してしまう「仕様」になっていることにあるらしい。てかフツー、ユーザーのレス待つっしょ。そもそもモバイル端末向けでリソースが限られているので、やれるものはとっとと片付けちまおう、っていう理念は分かるけどさ。

 android / java の解説サイトならびに解説本では、ほぼすべてのサイトにおいて activity ごとにバンバン別ファイルを作っているが、私は状態遷移が管理しにくくなって気色悪いので、今まで(といっても2か月弱だが)頑なに MainActivity.java の中にインナークラスを作ったり、分岐を工夫するなどして対応してきた。

 しかし、いよいよ折れざるを得ないときが来たようだ。2週間近くインターネットを放浪していたが、クリックされるまで先走らないように確実に止めておくためには、クリック検知アクティビティを別ファイルにしてそこに飛ばして、元ファイルに戻り値を返すことしか解決策が見当たらない。「仕様」と言われたらしようがない。

 にしても、オレのような気色悪さを感じる人はそんなに多くないらしい。みな android / java の仕様に合わせて、バンバン作った別アクティビティ・クラス(ファイル)間を intent で飛ばしまくっているからなのか、同様の課題を抱えている人をネット広しと言えども見出すことはほとんど皆無だった。(訴えは1件あったが解決策が書いていなかった)
 もとい、私はついに軍門に下った。

  • MainActivity.java

public class MainActivity extends Activity {
	
    static int a = 0;
    private static final int SUBACTIVITY = 1;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Intent intent = new Intent(this, SubActivity.class);
        startActivityForResult(intent, SUBACTIVITY);
	}
	
    @Override
    protected void onActivityResult(int requestCode,
                       int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == SUBACTIVITY) {
    	if (resultCode == RESULT_OK) {
    	        a = intent.getIntExtra("var", 0);
    	        final TextView tv = (TextView) findViewById(R.id.textview1);
    	        tv.setText("Result: " + a);
    	}
        }
    }
        
}

  • SubActivity.java

public class SubActivity extends Activity {
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	Intent intent = getIntent();

	final AlertDialog.Builder adb = new AlertDialog.Builder(this);
	adb.setMessage("SELECT")
	    .setCancelable(false)
    	    .setPositiveButton("YES",
    		    new DialogInterface.OnClickListener() {
	        public void onClick(DialogInterface dialog, int which) {
		Intent intent = new Intent();
		intent.putExtra("var", 100);
		setResult(RESULT_OK, intent);
		finish();
	        }
	    }
                )
    	    .setNegativeButton("NO",
    		    new DialogInterface.OnClickListener() {
    	        public void onClick(DialogInterface dialog, int which) {
		Intent intent = new Intent();
		intent.putExtra("var", 200);
		setResult(RESULT_OK, intent);
		finish();
 	        }
	    }
                )
	    .show();
		
    }	
}

 あと、AndroidManifest.xml に SubActivity を <activity> として登録すれば完成。

 軍門には下ったが、問題は解決された。ただし鶏口牛後。

ページトップへ