こゆのあったんだわ!

オプションの選択とかの金額とか店舗で話してもなんかめんどくさいすよね.
とうぜんWEBなら計算できたり画像で確認できたり.


試乗しないのであれば, Amazonで買ってもいいように思ったり.
こゆのあったんだわ!

オプションの選択とかの金額とか店舗で話してもなんかめんどくさいすよね.
とうぜんWEBなら計算できたり画像で確認できたり.


試乗しないのであれば, Amazonで買ってもいいように思ったり.

例えば, RecyclerView で一覧を作成して表示しているとして
1. 田中一郎
2. 佐藤二郎
3. 鈴木三郎
....
サーバなどに問い合わせて取得された以下データ
1. 田中一郎
2. ジローラモ
3. 鈴木三郎
...
を一覧画面に反映させたい場合.
RecyclerView.Adapter に更新を通知する場合,
RecyclerView.Adapter | Android Developers
notify*() のうちアイテムの更新に関するものは
notifyDataSetChanged()
notifyItemChanged(int position, Object payload)
notifyItemChanged(int position)
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
notifyItemRangeChanged(int positionStart, int itemCount)
ですが, それぞれ以下の面倒な部分がありました.
notifyDataSetChanged()
→ 変更部分のみに関してのアニメーションが表示されない.
notifyItemChanged(int position, Object payload)
notifyItemChanged(int position)
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
notifyItemRangeChanged(int positionStart, int itemCount)
→ アニメーションは表示されるが, 変更部分の position を計算して渡す必要がある.
その他 hasStableIds() と Loader を使ったりなど面倒でしたよね.
support-library-v7 24.2.0 で登場の DiffUtil を使いましょう.
コールバッククラス作成後, それを使った更新メソッドを Adapter 内に記述します.
MyDiffUtilCallback.java
public class MyDiffUtilCallback extends DiffUtil.Callback{
List<Person> oldPersons;
List<Person> newPersons;
public MyDiffUtilCallback(List<Person> oldPersons, List<Person> newPersons) {
this.oldPersons = oldPersons;
this.newPersons = newPersons;
}
@Override
public int getOldListSize() {
return oldPersons.size();
}
@Override
public int getNewListSize() {
return newPersons.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldPersons.get(oldItemPosition).id == newPersons.get(newItemPosition).id;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldPersons.get(oldItemPosition).equals(newPersons.get(newItemPosition));
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
// some additional information
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
MyRecyclerViewAdapter.java
public void updateList(ArrayList<Person> newList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtilCallback(this.persons, newList));
diffResult.dispatchUpdatesTo(this);
}
これで, アイテムのポジションの計算はおまかせにして, 同時に綺麗なアニメーションが変更部分に適用されます.
実際, データ量が多い場合は厳しいようなので, RxJava なら メインスレッドにて以下.
@Override
public void onNext(DiffUtil.DiffResult result) {
result.dispatchUpdatesTo(mProductAdapter);
}
kotlinなら同様に以下。
internal class DiffUtilCallback(
private val oldItems: List<Item>,
private val newItems: List<Item>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int)
= oldItems[oldItemPosition].id == newItems[newItemPosition].id
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int)
= oldItems[oldItemPosition] == newItems[newItemPosition]
}
以下のように使うと便利かもしれません.
更新されてます, Gradle が 3.0 に.

Gradle 3.0 released - News - Gradle Forums

The Gradle Daemon is a key performance enhancer, making builds up to 75% faster, but it needed to be explicitly enabled in previous versions of Gradle. This is no longer necessary as the Daemon is now enabled by default in 3.0.
Daemon がデフォルトで起動され, 75% の速度向上らしいので.
プロジェクトルートの位置で.
./gradlew wrapper --gradle-version=3.0
これで, Gradle 側はラッパーインストールできますが, Android Plugin が 2.1.x系では対応できていないようです.
最新のbetaを入れてみましょう.

com.android.tools.build.gradle
(project-root)/build.gradle
buildscript {
//System.properties['com.android.build.gradle.overrideVersionCheck'] = 'true'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0-beta1'
}
}
Total time: 6.543 secs
↓
Total time: 1.472 secs
小さいプロジェクトでも, 2回目以降のビルドで, 高速になったことが体感できます.
まあ, beta ということで少し待っていたほうがいいのかも知れませんが.
Square 生まれのライブラリを使います.
SQLBrite
SQLiteOpenHelper をラップするように使い, ContentProvider(ContentResolver) のように更新を通知する役割を担当します.

SQLBrite: A Reactive Database Foundation
RxJava と連携させて使いましょう.
SqlBrite sqlBrite = SqlBrite.create();
BriteDatabase db = sqlBrite.wrapDatabaseHelper(openHelper, Schedulers.io());
// 対象テーブル名を含むSQLクエリーを登録
Observable<Query> users = db.createQuery("users", "SELECT * FROM users");
final AtomicInteger queries = new AtomicInteger();
// データ更新を監視して受け取る
users.subscribe(new Action1<Query>() {
@Override public void call(Query query) {
queries.getAndIncrement();
}
});
System.out.println("Queries: " + queries.get()); // Prints 1
db.insert("users", createUser("jw", "Jake Wharton"));
db.insert("users", createUser("mattp", "Matt Precious"));
db.insert("users", createUser("strong", "Alec Strong"));
System.out.println("Queries: " + queries.get()); // Prints 4
このしくみを利用すると,
CursorLoader + ContentProvider
の部分を
RxJava + SQLBrite
で置き換えてリアクティブに記述できます.
googlesamples/android-architecture at dev-todo-mvp-rxjava
Observable を DataSource で作成して,
TasksLocalDataSource.java:77
@Override
public Observable<List<Task>> getTasks() {
String[] projection = {
TaskEntry.COLUMN_NAME_ENTRY_ID,
TaskEntry.COLUMN_NAME_TITLE,
TaskEntry.COLUMN_NAME_DESCRIPTION,
TaskEntry.COLUMN_NAME_COMPLETED
};
String sql = String.format("SELECT %s FROM %s", TextUtils.join(",", projection), TaskEntry.TABLE_NAME);
return mDatabaseHelper.createQuery(TaskEntry.TABLE_NAME, sql)
.mapToList(mTaskMapperFunction);
}
Repository 経由させて,
TaskRepository:96
@Override
public Observable<List<Task>> getTasks() {
// ...
if (mCacheIsDirty) {
return remoteTasks;
} else {
// Query the local storage if available. If not, query the network.
Observable<List<Task>> localTasks = mTasksLocalDataSource.getTasks();
return Observable.concat(localTasks, remoteTasks).first();
}
}
Presenter 上で subscribe しておけば,
TaskPresenter:104
Subscription subscription = mTasksRepository
.getTasks()
.flatMap(new Func1<List<Task>, Observable<Task>>() {
@Override
public Observable<Task> call(List<Task> tasks) {
return Observable.from(tasks);
}
})
.filter(new Func1<Task, Boolean>() {
// ...
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Task>>() {
@Override
public void onCompleted() {
mTasksView.setLoadingIndicator(false);
}
@Override
public void onError(Throwable e) {
mTasksView.showLoadingTasksError();
}
@Override
public void onNext(List<Task> tasks) {
processTasks(tasks);
}
});
データベースで対象テーブルに更新されるとすぐに自動的に再読み込みが実行されて画面が更新されます.
MVP に CursorLoader を利用すると, Presenter で受け取るコールバックが煩雑になりやすいのでこれを使うと便利です.

話題でした.
「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した - Qiita
これを幼稚に理解しておこうかとな.
「依存性注入」てのがしっくりこないので少し調べてみる.
「オブジェクトを注入する」と言うと日本語としても意味がはっきりしていますね。
「Dependency」は「依存性」ではなく「使われる側のオブジェクト」(依存オブジェクト)という意味になります。
やはりあなた方のDependency Injectionはまちがっている。 — A Day in Serenity
「外部オブジェクトの注入」と覚えたら直感的にわかりやすいですよね.
修正前.
メソッド内で外部オブジェクトを生成している.
public class RealBillingService implements BillingService {
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
CreditCardProcessor processor = new PaypalCreditCardProcessor();
TransactionLog transactionLog = new DatabaseTransactionLog();
// ...
}
}
修正後.
外部オブジェクトを外部で生成してコンストラクタから注入している.
public class RealBillingService implements BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
public RealBillingService(CreditCardProcessor processor,TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
// ...
}
}
これをテスト時には, メンバ初期化で生成したオブジェクトを各メソッド内でコンストラクタから注入して利用する.
public class RealBillingServiceTest extends TestCase {
private final PizzaOrder order = new PizzaOrder(100);
private final CreditCard creditCard = new CreditCard("1234", 11, 2010);
private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();
public void testSuccessfulCharge() {
RealBillingService billingService
= new RealBillingService(processor, transactionLog);
Receipt receipt = billingService.chargeOrder(order, creditCard);
// assert etc...
}
}
Repository クラスに, RemoteDataSource オブジェクトを注入しておいてテスト時には切り替えます.
それぞれのインスタンスは Singleton によって一意であることを保証しています.
公開用コード.
android-architecture/Injection.java at todo-mvp · googlesamples/android-architecture
public class Injection {
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
return TasksRepository.getInstance(TasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(context));
}
}
テスト用コード.
android-architecture/Injection.java at todo-mvp · googlesamples/android-architecture
public class Injection {
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(context));
}
}
外部オブジェクトの変更が容易になっていることがよくわかります.
注入の方法は他にもあるようですがまあいいか.
メソッド外で生成してコンストラクタから注入する.
(メソッド内で生成しない)