2014年2月23日日曜日

Android - AsyncTaskLoaderが何度も呼ばれる

AsyncTaskLoaderを使用していて、意図しないタイミングで何度も実行されていたので、この方法が正しいのかどうかは、また分かっていないのですが、とりあえず、一度だけ実行するように修正したので、メモ。

public class TestFragment extends Fragment {

    private static final int ID = 0;
    private LoaderManager mLoaderManager;

    public TestFragmen() {}

    LoaderManager.LoaderCallbacks<D> testCallbacks = new LoaderManager.LoaderCallbacks<D>() {

        @Override
        public void onLoadFinished(Loader<d> loader, D data) {;
            mLoaderManager.destroyLoader(ID);
        }
    };

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mLoaderManager = getLoaderManager();
        mLoaderManager.initLoader(ID, null, testCallbacks);
    }

}

ポイントは、onLoadFinished()で、LoaderManager#destroyLoader()を呼び出しているところです。
このLoaderManager#destroyLoader()が呼び出されると、Loader#onReset()が呼び出されるので、その中で、停止処理を行ってあげれば、終了してくれます。

public class TestLoader extends AsyncTaskLoader {

    @Override
    protected  void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected  void onReset() {
        super.onReset();
        onStopLoading();
    }

}

とりあえず、こんなところかな。


参考サイト:
LoaderManager | Android Developers

2014年2月22日土曜日

Android - startActivityForResult()とsingleTask

startActivityForResult()でハマったので、メモ。

startActivityForResult()でブラウザーを立ち上げて、閉じたときの結果を取得したいと思っていたのですが、

android:launchMode="singleTask"

で、起動しているActivityからstartActivityForResult()でブラウザーを立ち上げると、すぐにonActivityResult()に結果が返ってくるという事態に陥りまして、あれこれ調べていたら、どうもそういう仕様のようでした。

ちなみに、帰ってくる結果は、

RESULT_CANCELED

です。

実機でデバッグしていた時には、ちゃんと閉じたタイミングで結果が返ってきていたんですが、エミュレーターでは、すぐに結果が返ってきます。

「こういう動きが違う時には、ダメな方を採用する。」

さて、新たなカラクリを考えないと。
ほかにブラウザーを立ち上げて、閉じたときの結果を取得する方法ってあるかなぁ~。


参考サイト:
Android Up の振る舞いパターンを実装する

2014年2月21日金曜日

Android - DialogFragmentと画面回転

画面回転の際、DialogFragmentの設定情報がnullになってしまい、NullPointerExceptionで落ちてしまったので、対策メモ。

画面が回転すると、Activityの再構築が始まるのですが、その際、普通に値を設定すると再構築の際に値がクリアーされてしまいます。

public static class TestDialogFragment extends DialogFragment {
    public TestDialogFragment() {}

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // こんな感じ
        ProgressDialog progressDialog = new ProgressDialog(getActivity());
        progressDialog.setMessage("処理中です...");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        return progressDialog;
    }
}

今回は、Bundleに設定を保存するという方法で回避しました。

TestFragment.java
public class TestFragment extends Fragment {
    private static final String TAG = "dialog";
    private static TestDialogFragment sTestDialogFragment;
    
    public void ConfigurationFragment() {}
    
    LoaderManager.LoaderCallbacks<Cursor> testCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            sTestDialogFragment = TestDialogFragment.newInstance();
            sTestDialogFragment.show(getFragmentManager(), TAG);
            return new CursorLoader(getActivity(), …);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            sTestDialogFragment.getDialog().dismiss();
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
        }
    };
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, testCallbacks);
    }
    
    public static class TestDialogFragment extends DialogFragment {
        public TestDialogFragment() {}
        public static TestDialogFragment newInstance() {
            TestDialogFragment testDialogFragment = new TestDialogFragment();
            Bundle bundle = new Bundle();
            bundle.putString("message", "処理中です...");
            bundle.putInt("style", ProgressDialog.STYLE_SPINNER);
            testDialogFragment.setArguments(bundle);
            return testDialogFragment;
        }
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            Bundle bundle = getArguments();
            ProgressDialog progressDialog = new ProgressDialog(getActivity());
            progressDialog.setMessage(bundle.getString("message"));
            progressDialog.setProgressStyle(bundle.getInt("style"));
            return progressDialog;
        }
    }
}

ポイントは、TestDialogFragmentのインスタンスを作成する際、
TestDialogFragment prosessedDialogFragment = new TestDialogFragment();
のように取得するのではなく、TestDialogFragmentを返すメソッドを作成(newInstance())し、そのメソッド経由でインスタンスを取得します。

さらに、そのメソッド内で設定情報をBundleを使用してTestDialogFragmentのインスタンスの引数としてセットします。

ProgressDialogを作成する時は、インスタンスにセットされた引数を使って、値を設定します。

このような手順でProgressDialogを作成すると、画面が回転してActivityが再構築されても値はクリアーされません。

今回は、ProgressDialogでしたが、同じように値を持ったView等があった場合も、同じような手順で作成すれば、値がクリアーされることなく画面回転ができると思います。

注意点として、FragmentのインナークラスであるTestDialogFragmentは、「static」で作成しないと、怒られてしまいます。詳しい理由までは分かっていませんが、おまじないのように「static」をつけるようにしましょう。

あと、ProgressDialogを消す際の、dismiss()ですが、
sTestDialogFragment.dismiss();
のように、普通に消そうとすると落ちます。
sTestDialogFragment.getDialog().dismiss();
のように、getDialog()経由でDialogを取得すると正常に消えてくれます。


こんなところかな。

Android - ContentProvider

ContentProviderを扱ったので、メモ。

初めは、さっぱり理解できなかったが、四苦八苦しながら作っていくうちにContentProviderの便利さが少しずつ分かってきました。

ContentProviderは、アプリ間でデーターを共有する仕組み。


1. AndroidManifest.xmlにproviderを設定。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test" >
    <application>
        <provider
            android:name="com.example.test.provider.TestProvider"
            android:authorities="com.example.test.provider"
            android:exported="false" />
    </application>
</manifest>
 
android:name
これは、ContentProviderを実装しているクラス名。
上記の例でみると、
 
「com.example.test」パッケージのサブ「provider」にある「TestProvider」クラス
 
という事になります。
また、もう一つの記述方法として、
 
.provider.TestProvider
 
と記述する事も出来ます。
一文字目の「.」がパッケージ名に置き換わります。
 
android:authorities
ここには、ContentProviderを識別する重複しない名前を設定。
通常は、パッケージ名。
 
android:exported
これは、他のアプリとデータを共有するかどうか。
デフォルトは、true(共有する)
 
この他にも、たくさん設定項目がありますが、基本はこの三つかな。


2. ContentProviderを継承したクラスの作成

TestProvider.java
public class TestProvider extends ContentProvider {
    private DatabaseOpenHelper mDatabaseOpenHelper;
    
    @Override
    public boolean onCreate() {
        mDatabaseOpenHelper = new DatabaseOpenHelper(getContext());
        return true;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        SQLiteDatabase readableDb = mDatabaseOpenHelper.getReadableDatabase();
        queryBuilder.setTables(uri.getPathSegments().get(0));
        Cursor cursor = queryBuilder.query(readableDb, projection, selection, selectionArgs, null, null, sortOrder);
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase writableDb = mDatabaseOpenHelper.getWritableDatabase();
        String tableName = uri.getPathSegments().get(0);
        long rowId = writableDb.insertOrThrow(tableName, null, values);
        Uri newURI = ContentUris.withAppendedId(uri, rowId);
        getContext().getContentResolver().notifyChange(newURI, null);
        return newURI;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase writableDb = mDatabaseOpenHelper.getWritableDatabase();
        String tableName = uri.getPathSegments().get(0);
        int rowNumber = writableDb.update(tableName, values, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return rowNumber;
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase writableDb = mDatabaseOpenHelper.getWritableDatabase();
        String tableName = uri.getPathSegments().get(0);
        int rowNumber = writableDb.delete(tableName, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return rowNumber;
    }
    
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

DatabaseOpenHelper.java
public class DatabaseOpenHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "test.sqlite";
    private static final CursorFactory FACTORY = null;
    private static final int DATABASE_VERSION = 1;

    public DatabaseOpenHelper(Context context) {
        super(context, DATABASE_NAME, FACTORY, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

特に、難しいことはしていません。
query()以外は、データーベースの内容を変更した後、ContentResolverに対してnotifyChange()を呼び出して、データーベースの内容が変更したことを教えています。

getType()は、今の段階ではよく分かりませんでした。ブレークポイントを置いても止まってくれず、どのタイミングで呼び出されるのかが不明です。名前の通り、データーの種類を取得するのだと思うのですが、その辺は必要になった時に改めて調べてみるつもりです。


3. ContentProviderを使う

TestFragment.java
public class TestFragment extends Fragment {
    private static final String PROVIDER_NAME = "com.example.test.provider";
    private static final String TABLE_NAME = "test";
    private static final String USER_ID = "user_id";
    private List<String> mUserId;
    
    public void ConfigurationFragment() {}
    
    LoaderManager.LoaderCallbacks<Cursor> userIdCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            final String URI = "content://" + PROVIDER_NAME + File.separator + TABLE_NAME;
            final Uri CONTENT_URI = Uri.parse(URI);
            String[] projection = { USER_ID };
            String selection = null;
            String[] selectionArgs = null;
            String sortOrder = null;
            return new CursorLoader(getActivity(), CONTENT_URI, projection, selection, selectionArgs, sortOrder);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            if (data.getCount() > 0) {
                while(data.moveToNext()) {
                    mUserId.add(data.getString(0));
                }
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
        }
    };
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mUserId = new ArrayList<String>();
        insertUserId();
        getLoaderManager().initLoader(0, null, userIdCallbacks);
    }

    public void insertUserId() {
        final String URI = "content://" + PROVIDER_NAME + File.separator + TABLE_NAME;
        final Uri CONTENT_URI = Uri.parse(URI);
        ContentValues contentValus = new ContentValues();
        contentValus.put(USER_ID, "ユーザーID");
        Uri uri = getActivity().getContentResolver().insert(CONTENT_URI, contentValus);
        contentValus.clear();
    }
}
 
ContentProviderを使うためには、「content://」で始まるURIを作成する必要があります。
PROVIDER_NAMEには、AndroidManifest.xmlのandroid:authoritiesで設定した値が入ります。そして、最後には対象のデーターベースのテーブル名が入ります。
 
もっと細かな設定が出来るのですが、今回はデーターベースのテーブルに対して操作をしたかったので、この設定で十分でした。
 
データーベースへの操作が多くなってくると、このContentProviderを経由したやり方がとても楽に感じます。まだ初歩的な使い方しかできていませんが、ContentProviderを使っていくうちにもう少し知識が増えたら、またこのブログに掲載したいと思っています。
 
こんなところかな。

2014年2月17日月曜日

Android - ListFragment と FragmentPagerAdapter の相性問題解決

あんなにいろいろ試したのになぁ~。
下記のサイトのおかげで、すんなりエラーが消えてくれました。

GetItem(int) to return Listfragment in FragmentPagerAdapter

このサイトも、確認したんだけどなぁ~。
確認漏れか…。

正解は、

// こちらではなく、
import android.app.ListFragment;

// こちらをインポートする。
import android.support.v4.app.ListFragment; 

でした。

これも、一度試したんだけどなぁ~。
ミスったか…

なにはともあれ、これで次へ進めます。

でも、なんで「android.app.ListFragment」だとダメなんだろう。


追記:2014/02/23
android.app.ListFragmentがダメなのが気になって、もう一度作り直してみたのですが、そうしたら
import android.app.ListFragment;
の方でもエラーが出なくなりました。

エラーが出なくなったコードも載せておきます。
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return TestListFragment.newInstance();
        case 1:
            return PlaceholderFragment.newInstance(position + 1);
        case 2:
            return PlaceholderFragment.newInstance(position + 1);
    }
    return null;
}
TestListFragment.java
public class TestListFragment extends ListFragment {
    public  TestListFragment() {}
    public static TestListFragment newInstance() {
        TestListFragment testListFragment = new TestListFragment();
        return testListFragment;
    }
}
 
 
これで、すっきりしました。

Android Studio - 設定メモ

今まで、Android Developer Toolsを使ってきたが、新規プロジェクトの立ち上げを期にAndroid Studioへ移行。

その際に、設定した事を。


File - Settings - Appearance

・日本語の文字化け対策
Override default font by (not reccomended): にチェックをつけて、日本語が表示できるフォントへ変更。


File - Settings - Editor - Appearance

・行番号表示
Show line numbers にチェック。

・スペース等の表示。
Show whitespaces にチェック。

・エディター内に表示されている縦線を非表示
Show right margin (configured in Code Style options) のチェックを外す。


File - Settings - Editor - Colors & Fonts - Font

・Scheme name の作成
「Save As」ボタンを押し、新規のスキーマを作成。

・Editor Font の設定
Show only monospaced fonts のチェックを外し、Primary font を選択。


File - Settings - Editor - Colors & Fonts - General以下

・Italicを全て外す
斜めは読みづらいので、ことごとく全て外す。


Android Studio インストールフォルダ - bin

・Android Studio の表示フォントをキレイにする
32ビットの場合は、studio.exe.vmoptions ファイル。
64ビットの場合は、studio64.exe.vmoptions ファイル。
をメモ帳等で開き、最後の行に、

-Dawt.useSystemAAFontSettings=lcd

を入力し、保存。
Android Studio を再起動させると、驚くほどフォントがキレイに!!


こんなところかな。

Android - ListFragment と FragmentPagerAdapter の相性

アプリの作成を始めて約二ヶ月。

一通りの事は出来たので、現在作成しているアプリを『タブ+フラグメント』を使用した形に変えようを思い、新規プロヘクトを立ち上げ。

フラグメントは初めてだったので、自動で作成されるコードをながめ、仕組みを勉強。

今まで作成してきたアプリでは、LiatActivityを使用していたので、新規プロジェクトでは、ListFragmentを使って作成しようと思い、ListFragmentを継承したクラスを作成したまではよかったが、FragmentPagerAdapter内のgetItemメソッドの戻り値が『Fragment』という事で、型が違うと怒られてしまう。

ListFragmentからFragmentへキャストを試みるも、あえなく撃沈。

「さて、どうしたものか」

FragmentPagerAdapter | Android Developers 内のサンプルでは、ListFragmentを使用しているのだが、その通り試してみるも、同じく型が違うと怒られてしまう。

出だしでハマッてしまった。

すんなりいく解決策はあるのだろうが、何せAndroid二ヶ月の初心者には、ハードルが高い問題だ。

Fragmentを継承したクラスで作成するしかないのかなぁ~。

なんか悔しい。

もう少しもがいてみるか。