読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

RoboGuice2事始め 2

前回の「RoboGuice2事始め」の続き

  1. ボタンを押すと、外部のカメラアプリを起動して
  2. 撮影した結果を受け取って
  3. ImageViewにプレビュー表示する

という件で、普段の開発時には1ボタンを押す->3ImageViewにプレビュー表示する、といった具合に2を飛ばすようにしたいんですよ。いちいちカメラ起動とかタルいし。

で、なんとなく5割くらいできて、こっから先、検討がつかなくなってきたので一旦区切りをつけることに。

整理

実は、あまりJavaはあまりさわったことがない上に、DI!DI!といってもちゃんと実用したことがなかったので、「あー、DIね。あ、Guiceはこの手のタイプのやつね」といったノリでホイホイ導入できるような能力は持ってない。

いろいろドキュメント等は読んでみるけど、実際に導入してみようと思うとまったくイメージがわかない・・・。「イメージがわかないのは、今のコードがわかりにくいからだ」ということにして、とりあえずコードをシンプルにしてみるべく、整理を進めてみた。

前回コード

public class SampleActivity extends RoboFragmentActivity {
    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    @InjectView(R.id.btn_camera) ImageButton cameraButton;
    @InjectView(R.id.btn_save) Button saveButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.Theme_Sherlock_Light_DarkActionBar);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.sample);

        cameraButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                // TODO data should be saved on sdcard
//                Uri fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
//                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);

                startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);

            }
        });

        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });

    }


整理後

public class SampleActivity extends RoboFragmentActivity {
    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    @InjectView(R.id.btn_camera) ImageButton cameraButton;
    @InjectView(R.id.btn_save) Button saveButton;
    @InjectView(R.id.preview) ImageView preview;


    View.OnClickListener cameraListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
        }
    };

    View.OnClickListener saveListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    };


    @Override
    public void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.Theme_Sherlock_Light_DarkActionBar);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.sample);

        setListener(cameraButton, cameraListener);
        setListener(saveButton, saveListener);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                // this image is not enough for save!!
                Bitmap bitmap = (Bitmap) data.getExtras().get("data");
                preview.setImageBitmap(bitmap);
            } else if (resultCode == RESULT_CANCELED) {
                Log.i(this.getClass().getName(), "camera canceled");
            } else {
                Toast.makeText(this, "failed", Toast.LENGTH_LONG).show();
            }
        }
    }

onClickイベントのリスナーとかをonCreateから出してみた。まだ、イメージがわかない。。再び各種ドキュメント等を眺めてみる。。。

と、Eventを制御するAPIがあった。これじゃね?
http://code.google.com/p/roboguice/wiki/Events

これを組み込んで見ること1時間あまり・・・。

public class SampleActivity extends RoboFragmentActivity {
    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    @InjectView(R.id.btn_camera) ImageButton cameraButton;
    @InjectView(R.id.btn_save) Button saveButton;
    @InjectView(R.id.preview) ImageView preview;

    @Inject protected EventManager eventManager;                        ・・・(1)

    View.OnClickListener cameraListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            PhotoEvent event = new MyPhotoEventMock();      ・・・(2)
            eventManager.fire(event);                 
        }
    };

    View.OnClickListener saveListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    };


    @Override
    public void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.Theme_Sherlock_Light_DarkActionBar);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.sample);

        setListener(cameraButton, cameraListener);
        setListener(saveButton, saveListener);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                // this image is not enough for save!!
                Bitmap bitmap = (Bitmap) data.getExtras().get("data");
                preview.setImageBitmap(bitmap);
            } else if (resultCode == RESULT_CANCELED) {
                Log.i(this.getClass().getName(), "camera canceled");
            } else {
                Toast.makeText(this, "failed", Toast.LENGTH_LONG).show();
            }
        }
    }

    protected void setListener(View v, View.OnClickListener l) {
        v.setOnClickListener(l);
    }

    private static Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }

    private static File getOutputMediaFile(int type) {
        // TODO SDCard mount check
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "VID_" + timeStamp + ".mp4");
        } else {
            return null;
        }

        return mediaFile;
    }

    protected void handlePhoto( @Observes PhotoEvent event) {      ・・・(3)
        Toast.makeText(this, "handlePhoto", Toast.LENGTH_LONG).show();
        event.takePhoto(this);
    }

}
                                    ・・・(4)
interface PhotoEvent {
    public void takePhoto(SampleActivity context);
}

class MyPhotoEvent implements PhotoEvent {

    public void takePhoto(SampleActivity context) {
        Log.d("MyPhotoEvent", "let's take a photo.");

        // TODO data should be saved on sdcard
//                Uri fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
//                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        context.startActivityForResult(intent, 100);
    }

}

class MyPhotoEventMock implements PhotoEvent {
    public void takePhoto(SampleActivity context) {
        Log.d("MyPhotoEvent", "pretend to take a photo.");

        Drawable image = context.getResources().getDrawable(R.drawable.dummy);

        ImageView v = (ImageView) context.findViewById(R.id.preview);

        v.setImageDrawable(image);

    }
}

こんな感じになった。最初にいったとおり7割程度の完成度なのだけど。

(1)は名前のとおりの働きをする。おまじない的に記述しておく。
(2)でonClick時の振る舞い(この言葉、使ってみたかった)を制御する。fire()ってメソッド名がイイネ!
(3)メソッド名はなんでもよくて@Observesアノテーションをつけたクラス(この場合はPhotoEvent)が渡される。
(4)3で動くクラスを実装しておく

こんな感じなんだけど、これだと思ったとおりに動かない。

意図としては4でtakePhoto()を持ったPhotoEventインタフェースを用意して、それを継承した本チャンで使うクラスと開発中用のモッククラスを用意しておいて、2でそれを書き換える(できればもうちょっといじってAntだかMavenだかで自動的に制御したい)ことで3での振る舞いを変えられるようにするつもりだったんだけど、ダメだった。

2のeventManager.fire(event)の実装を追ってみると

    /**
     * Raises the event's class' event on the given context.  This event object is passed (if configured) to the
     * registered observer's method.
     *
     * @param event observed
     */
    public void fire(Object event) {

        final Set<EventListener<?>> observers = registrations.get(event.getClass());
        if (observers == null) return;

        // As documented in http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Collections.html#synchronizedSet(java.util.Set)
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (observers) {
            for (EventListener observer : observers)
                observer.onEvent(event);
        }

    }

こんな感じになっていて、@Observersで指定したクラスとfire()で渡したクラスが一致しないとスルーされるようになっている。ということですよね?


うーん。ここからどうしたらいいんだろう。
誰か教えて・・・。

東京だとAndroid絡みの勉強会イベントとかあるけど湘南エリアからだとキツイんだよね。。誰かテラスモールあたりで情報交換しましょーよ。