かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Loaderを使ったデータの読み込み

最近のAndroidでのデータの読み込みはLoaderというものを使うらしいです。ちょっとやってみました。

オレオレ仕様にカスタムできるLoader

カーソル前提のLoaderもあるんですが、自前のデータ読み込み処理を書けるAsyncTaskLoaderというのがあるのでそれを使うのが一番柔軟っぽいです。

AsyncTaskLoaderは、継承してloadInBackgroundメソッドを実装するだけの簡単クラスです。例えばPersonという以下のようなクラスがあるとします。

// めんどいのでActivityの内部クラスにした
public static class Person {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return this.name;
    }
}

適当にこのクラスを生成する実装は以下のようになります。画面にeditTextという名前のEditTextを置いていて、そいつから取得したテキストをベースに先ほどのPersonクラスを組み立てては、Listに突っ込んでいます。時間のかかる処理っぽく少しSleepも入れています。

final Random r = new Random();
final String input = this.editText.getText().toString();
AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
    @Override
    public List<Person> loadInBackground() {
        Log.d("MyActivity", "loadInBackground");
        int count = r.nextInt(100);
        List<Person> result = new ArrayList<Person>();
        for (int i = 0; i < count; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Person p = new Person();
            p.setName(input + " " + i);
            result.add(p);
        }
        return result;
    }
};

Loaderを使うためのコールバックインターフェース

Javaってコールバック地獄でめんどくさいですよね…。Loaderをバックグラウンドで実行するために、以下のインターフェースを実装しないといけません。これは先ほど示したAsyncTaskLoaderクラスとは別ものです。LoaderManager.LoaderCallbacksというインターフェースになります。Tは、AsyncTaskLoaderのTと合わせて使います。

LoaderCallbacksには、以下の3つのメソッドがあります。

  • onCreateLoader: Loaderを作るメソッド
  • onLoadFinished: Loaderの処理が終わった後に呼ばれるメソッド
  • onLoaderReset: リセットしたときに呼ばれるメソッド

ActivityにLoaderCallbacksインターフェースを実装するサンプルをよく見るのでここでもそうしました。

まず、onCreateLoaderメソッドです。こいつはさっきのAsyncTaskLoaderを作成しています。 そして、Loaderの処理をキックするforceLoadメソッドを呼んでいます(大事)

@Override
public Loader<List<Person>> onCreateLoader(int id, Bundle args) {
    Log.d("MyActivity", "onCreateLoader");
    Random r = new Random();
    final String input = this.editText.getText().toString();
    AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
        @Override
        public List<Person> loadInBackground() {
            Log.d("MyActivity", "loadInBackground");
            int count = r.nextInt(100);
            List<Person> result = new ArrayList<Person>();
            for (int i = 0; i < count; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person p = new Person();
                p.setName(input + " " + i);
                result.add(p);
            }
            return result;
        }
    };
    loader.forceLoad();
    return loader;
}

そして、onLoadFinishedメソッドで読み込みが終わったときの処理を書きます。ここではArrayAdapterにデータをセットしています。

@Override
public void onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
    Log.d("MyActivity", "onLoadFinished");
    this.peopleAdapter.clear();
    this.peopleAdapter.addAll(data);
}
@Override
public void onLoaderReset(Loader<List<Person>> loader) {
}

ついでに、何もしないonLoaderResetも書いておきました。

コード全体

レイアウトファイル。EditTextとListView置いただけの画面

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView" />
</LinearLayout>

Activityに全部処理かいてます///

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class MyActivity extends Activity implements LoaderManager.LoaderCallbacks<List<MyActivity.Person>>  {

    private EditText editText;
    private ListView listView;
    private ArrayAdapter<Person> peopleAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.editText = (EditText) this.findViewById(R.id.editText);
        this.listView = (ListView) this.findViewById(R.id.listView);
        this.peopleAdapter = new ArrayAdapter<Person>(this, android.R.layout.simple_list_item_1);
        this.listView.setAdapter(this.peopleAdapter);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            Log.d("MyActivity", "onOptionsItemSelected");
            this.getLoaderManager().restartLoader(0, null, this);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    public Loader<List<Person>> onCreateLoader(int id, Bundle args) {
        Log.d("MyActivity", "onCreateLoader");
        final Random r = new Random();
        final String input = this.editText.getText().toString();
        AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
            @Override
            public List<Person> loadInBackground() {
                Log.d("MyActivity", "loadInBackground");
                int count = r.nextInt(100);
                List<Person> result = new ArrayList<Person>();
                for (int i = 0; i < count; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Person p = new Person();
                    p.setName(input + " " + i);
                    result.add(p);
                }
                return result;
            }
        };
        loader.forceLoad();
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
        Log.d("MyActivity", "onLoadFinished");
        this.peopleAdapter.clear();
        this.peopleAdapter.addAll(data);
    }

    @Override
    public void onLoaderReset(Loader<List<Person>> loader) {

    }


    public static class Person {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }
}