ViewModelコンポーネントは、ビューに関連付けられたデータを保存および管理すると同時に、画面の反転などの操作中にアクティビティを再作成する問題を軽減するように設計されています。 システムがアクティビティを破棄した後、たとえば別のアプリケーションに移動した場合、ViewModelも破棄され、その状態が保存されないため、onSaveInstanceStateの代わりとして使用しないでください。 一般に、ViewModelコンポーネントは、ViewModelクラスのインスタンスのコレクションを持つシングルトンとして記述できます。これにより、アクティビティのアクティブなインスタンスがある限り破棄されず、アクティビティを離れた後にリソースを解放します(すべてが少し複雑ですが、そのように見えます)。 また、ViewModelをいくつでもアクティビティ(フラグメント)にバインドできることにも注意してください。
コンポーネントは、
ViewModel、AndroidViewModel、ViewModelProvider、ViewModelProviders、ViewModelStore、ViewModelStoresのクラスで構成されています。 開発者はViewModel、AndroidViewModelのみを使用し、ViewModelProvidersで原告を取得しますが、コンポーネントをよりよく理解するために、すべてのクラスを表面的に検討します。
ViewModelクラス自体は、抽象メソッドを持たず、1つの保護されたメソッドonCleared()を持つ抽象クラスを表します。 独自のViewModelを実装するには、パラメーターなしのコンストラクターでViewModelクラスを継承する必要があります。それだけです。 リソースをクリアする必要がある場合、onCleared()メソッドを再定義する必要があります。このメソッドは、ViewModelが長時間利用できず、破棄する必要がある場合に呼び出されます。 例として、LiveData、特にobserveForever(Observer)メソッドに関する以前の記事を思い出すことができます。これは明示的な応答を必要とし、onCleared()メソッドで実装するのが適切です。 また、メモリリークを回避するために、ViewModelからViewまたはContextアクティビティを直接参照する必要がないことも追加する価値があります。 一般に、ViewModelはデータの表示から完全に分離されている必要があります。 この場合、問題が発生します。そして、データの変更をビュー(アクティビティ/フラグメント)にどのように通知するのでしょうか? この場合、LiveDataが助けになります。LiveDataを使用してすべての可変データを保存する必要がありますが、たとえばProgressBarを表示および非表示にする必要がある場合は、MutableLiveDataを作成し、ViewModelコンポーネントにshow \ hideロジックを保存できます。 一般的に、次のようになります。
public class MyViewModel extends ViewModel { private MutableLiveData<Boolean> showProgress = new MutableLiveData<>();
ViewModelのインスタンスへのリンクを取得するには、ViewModelProvidersを使用する必要があります。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); viewModel.getProgressState().observe(this, new Observer<Boolean>() { @Override public void onChanged(@Nullable Boolean aBoolean) { if (aBoolean) { showProgress(); } else { hideProgress(); } } }); viewModel.doSomeThing(); }
AndroidViewModelクラスはViewModel拡張機能です。唯一の違いは、コンストラクターにApplicationパラメーターが1つあることです。 これは、ロケーションサービスまたはアプリケーションコンテキストを必要とする別のコンポーネントを使用する必要がある場合に非常に便利な拡張機能です。 それを扱う際の唯一の違いは、ApplicationViewModelからViewModelを継承することです。 Activity / Fragmentでは、通常のViewModelと同様に初期化します。
ViewModelProvidersクラスは 、
ViewModelProviderを呼び出して返す4つのユーティリティメソッドです。 アクティビティとフラグメント、およびViewModelProvider.Factoryの実装を置き換える機能と連携するように適合されたDefaultFactoryがデフォルトで使用されます。これは、ViewModelProvidersのネストされたクラスです。 これまでのところ、android.archパッケージにリストされている他の実装はありません。
ViewModelProviderクラス、実際にはViewModelインスタンスを返すクラス。 ここではあまり詳しく説明しませんが、一般的に、彼はViewModelStoreの仲介者として機能します。 ポイントは、複数のViewModelを1つのタイプのアクティビティ/フラグメントにバインドできることです。 getメソッドは、デフォルトで「android.arch.lifecycle.ViewModelProvider.DefaultKey:」+ canonicalNameの形式のStringキーでそれらを返します。
ViewModelStoresクラスはファクトリーメソッドです。思い出してください。ファクトリーメソッドは、オブジェクトを作成するためのインターフェイスを定義するパターンですが、サブクラスにインスタンス化するクラスに関する決定を残します。実際、クラスはインスタンス化をサブクラスに委任できます。 現在、android.archパッケージには、ViewModelStoreの1つのインターフェイスと1つのサブクラスの両方が含まれています。
ViewModelStoreクラス(すべての魔法を含むクラス)は、put、get、およびclearメソッドで構成されます。 それらを直接操作する必要がないため、それらについて心配する必要はありません。また、それらはパッケージ内でのみ表示されるデフォルト(パッケージプライベート)として宣言されているため、物理的に取得および配置することはできません。 ただし、一般教育では、このクラスのデバイスを検討してください。 クラス自体は、HashMap <String、ViewModel>、getおよびputメソッドをそれぞれ格納し、キー(ViewModelProviderで作成したもの)ごとに返すか、ViewModelを追加します。 clear()メソッドは、追加したすべてのViewModelでonCleared()メソッドを呼び出します。
ViewModelの使用例については、ユーザーがマップ上のポイントを選択し、半径を設定し、このフィールドに人がいるかどうかを表示できる小さなアプリケーションを実装してみましょう。 WiFiネットワークを指定する機能を提供するだけでなく、ユーザーがそれに接続している場合、物理座標に関係なく半径内にあると想定します。まず、2つのLiveDataを作成して、WiFiネットワークの場所と名前を追跡します。
public class LocationLiveData extends LiveData<Location> implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { private final static int UPDATE_INTERVAL = 1000; private GoogleApiClient googleApiClient; public LocationLiveData(Context context) { googleApiClient = new GoogleApiClient.Builder(context, this, this) .addApi(LocationServices.API) .build(); } @Override protected void onActive() { googleApiClient.connect(); } @Override protected void onInactive() { if (googleApiClient.isConnected()) { LocationServices.FusedLocationApi.removeLocationUpdates( googleApiClient, this); } googleApiClient.disconnect(); } @Override public void onConnected(Bundle connectionHint) { LocationRequest locationRequest = new LocationRequest().setInterval(UPDATE_INTERVAL).setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationServices.FusedLocationApi.requestLocationUpdates( googleApiClient, locationRequest, this); } @Override public void onLocationChanged(Location location) { setValue(location); } @Override public void onConnectionSuspended(int cause) { setValue(null); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { setValue(null); } }
public class NetworkLiveData extends LiveData<String> { private Context context; private BroadcastReceiver broadcastReceiver; public NetworkLiveData(Context context) { this.context = context; } private void prepareReceiver(Context context) { IntentFilter filter = new IntentFilter(); filter.addAction("android.net.wifi.supplicant.CONNECTION_CHANGE"); filter.addAction("android.net.wifi.STATE_CHANGE"); broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); String name = wifiInfo.getSSID(); if (name.isEmpty()) { setValue(null); } else { setValue(name); } } }; context.registerReceiver(broadcastReceiver, filter); } @Override protected void onActive() { super.onActive(); prepareReceiver(context); } @Override protected void onInactive() { super.onInactive(); context.unregisterReceiver(broadcastReceiver); broadcastReceiver = null; } }
2つのLifeDataから受信したデータに依存する条件があるため、ViewModelに進みましょう。MediatorLiveDataは値の所有者として理想的ですが、サービスを再開することは有益ではないため、observerForeverを使用してライフサイクルを参照せずにMediatorLiveDataにサブスクライブします。 onCleared()メソッドでは、removeObserverを使用してサブスクライブを解除します。 次に、LiveDataはMutableLiveDataの変更を通知し、これにビューがサブスクライブされます。
public class DetectorViewModel extends AndroidViewModel {
そして私たちの見解:
public class MainActivity extends LifecycleActivity { private static final int PERMISSION_LOCATION_REQUEST = 0001; private static final int PLACE_PICKER_REQUEST = 1; private static final int GPS_ENABLE_REQUEST = 2; @BindView(R.id.status) TextView statusView; @BindView(R.id.radius) EditText radiusEditText; @BindView(R.id.point) EditText pointEditText; @BindView(R.id.network_name) EditText networkEditText; @BindView(R.id.warning_container) ViewGroup warningContainer; @BindView(R.id.main_content) ViewGroup contentContainer; @BindView(R.id.permission) Button permissionButton; @BindView(R.id.gps) Button gpsButton; private DetectorViewModel viewModel; private LatLng latLng; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); checkPermission(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { init(); } else { showWarningPage(Warning.PERMISSION); } } private void checkPermission() { if (PackageManager.PERMISSION_GRANTED == checkSelfPermission( Manifest.permission.ACCESS_FINE_LOCATION)) { init(); } else { requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_LOCATION_REQUEST); } } private void init() { viewModel = ViewModelProviders.of(this).get(DetectorViewModel.class); if (Utils.isGpsEnabled(this)) { hideWarningPage(); checkingPosition(); initInput(); } else { showWarningPage(Warning.GPS_DISABLED); } } private void initInput() { radiusEditText.setText(String.valueOf(viewModel.getRadius())); latLng = viewModel.getPoint(); if (latLng == null) { pointEditText.setText(getString(R.string.chose_point)); } else { pointEditText.setText(latLng.toString()); } networkEditText.setText(viewModel.getNetworkName()); } @OnClick(R.id.get_point) void getPointClick(View view) { PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder(); try { startActivityForResult(builder.build(MainActivity.this), PLACE_PICKER_REQUEST); } catch (GooglePlayServicesRepairableException e) { e.printStackTrace(); } catch (GooglePlayServicesNotAvailableException e) { e.printStackTrace(); } } @OnClick(R.id.save) void saveOnClick(View view) { if (!TextUtils.isEmpty(radiusEditText.getText())) { viewModel.saveRadius(Integer.parseInt(radiusEditText.getText().toString())); } viewModel.saveNetworkName(networkEditText.getText().toString()); } @OnClick(R.id.permission) void permissionOnClick(View view) { checkPermission(); } @OnClick(R.id.gps) void gpsOnClick(View view) { startActivityForResult(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS), GPS_ENABLE_REQUEST); } private void checkingPosition() { viewModel.getStatus().observe(this, new Observer<String>() { @Override public void onChanged(@Nullable String status) { updateUI(status); } }); } private void updateUI(String status) { statusView.setText(status); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PLACE_PICKER_REQUEST) { if (resultCode == RESULT_OK) { Place place = PlacePicker.getPlace(data, this); updatePlace(place.getLatLng()); } } if (requestCode == GPS_ENABLE_REQUEST) { init(); } } private void updatePlace(LatLng latLng) { viewModel.savePoint(latLng); pointEditText.setText(latLng.toString()); } private void showWarningPage(Warning warning) { warningContainer.setVisibility(View.VISIBLE); contentContainer.setVisibility(View.INVISIBLE); switch (warning) { case PERMISSION: gpsButton.setVisibility(View.INVISIBLE); permissionButton.setVisibility(View.VISIBLE); break; case GPS_DISABLED: gpsButton.setVisibility(View.VISIBLE); permissionButton.setVisibility(View.INVISIBLE); break; } } private void hideWarningPage() { warningContainer.setVisibility(View.GONE); contentContainer.setVisibility(View.VISIBLE); } }
一般に、ViewModelのgetStatus()メソッドを使用してMutableLiveDataをサブスクライブします。 また、彼と協力してデータを初期化して保存します。
RuntimePermissionやGPSステータスチェックなど、いくつかのチェックもここに追加されました。 ご覧のとおり、Activityのコードは非常に広範囲であることが判明しました。複雑なUIの場合、Googleはプレゼンターを作成する方向に目を向けることを推奨します(ただし、これはやり過ぎです)。
この例では、次のようなライブラリも使用しました。
compile 'com.jakewharton:butterknife:8.6.0' compile 'com.google.android.gms:play-services-maps:11.0.2' compile 'com.google.android.gms:play-services-location:11.0.2' compile 'com.google.android.gms:play-services-places:11.0.2' annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
完全なリスト:
こちら便利なリンク:
ここと
ここAndroidアーキテクチャコンポーネント。 パート1.はじめにAndroidアーキテクチャコンポーネント。 パート2.ライフサイクルAndroidアーキテクチャコンポーネント。 パート3. LiveDataAndroidアーキテクチャコンポーネント。 パート4. ViewModel