- 投稿日:2020-02-28T22:25:05+09:00
Dagger2使用時に引数ありのViewModelをinterface化する
はじめに
本記事はDagger2 + MVVM構成のアーキテクチャで、引数ありのViewModelをinterface化する方法の備忘録です。
リポジトリ
以下に今回のサンプルを置いてあります。
https://github.com/nanaten/Dagger-ViewModel-Interfaceプロジェクト構成
昨今のAndroid開発における標準的なMVVMを想定しています。
View (Fragment) -> ViewModel -> Repository ( -> API )
注意書き
今回、基本的なDagger2の構成の解説は省略します。 今回はViewModelに関わる部分のみ解説します。
※Dagger2の基本構成は【DI】Dagger2+Retrofit2(+OkHttp3)+ViewModelのDIの最小構成で解説していますのでそちらを参照してください。今回のプロジェクト構成も上記記事とほぼ変わりません。
解説
ViewModelは以下のような作りになっています。
MainViewModelImpl
はコンストラクタにMainRepository
の引数を持ちます。MainViewModelinterface MainViewModel { ... } class MainViewModelImpl @Inject constructor(private val repository: MainRepository) : ViewModel(), MainViewModel { ... }コンストラクタ付きのViewModelを使うために
ViewModelKey
とViewModelFactory
を定義します。ViewModelFactory
についてはこちらの記事を参考にさせて頂いています。ViewModelKey@MustBeDocumented @Target( AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER ) @Retention(AnnotationRetention.RUNTIME) @MapKey internal annotation class ViewModelKey(val value: KClass<out ViewModel>)ViewModelFactoryclass ViewModelFactory @Inject constructor( private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { var creator: Provider<out ViewModel>? = creators[modelClass] if (creator == null) { for ((key, value) in creators) { if (modelClass.isAssignableFrom(key)) { creator = value break } } } if (creator == null) { throw IllegalArgumentException("unknown model class " + modelClass) } try { @Suppress("UNCHECKED_CAST") return creator.get() as T } catch (e: Exception) { throw RuntimeException(e) } } }作成したViewModelKeyを使って、ViewModelを以下のようにbindします。
@Binds @IntoMap @ViewModelKey(MainViewModelImpl::class) fun bindMainViewModel(viewModel: MainViewModelImpl): ViewModelbindしたModuleをinjectします。今回はFragmentで使いたかったのでAppComponentに書かずにActivityにmoduleを追加しました。
@Module interface MainActivityModule { @Binds @IntoMap @ViewModelKey(MainViewModelImpl::class) fun bindMainViewModel(viewModel: MainViewModelImpl): ViewModel @ContributesAndroidInjector fun bindMainFragment(): MainFragment }@ContributesAndroidInjector(modules = [MainActivityModule::class]) abstract fun bindMainActivity(): MainActivityそして、FragmentでViewModelProviderを使って以下のように宣言すれば、interface化したViewModelを扱えるようになります。
MainFragmentclass MainFragment : DaggerFragment() { @Inject lateinit var viewModelFactory: ViewModelFactory // MainViewModelImpl ではなく MainViewModel として型宣言する private val viewModel: MainViewModel by lazy { // 実態は MainViewModelImpl を生成する ViewModelProvider(this, viewModelFactory).get(MainViewModelImpl::class.java) } }型宣言をちゃんとしないと
MainViewModelImpl
として認識されてしまうのでご注意ください。ちなみにActivityでViewModelを使う場合もほぼ同じです。Activityで使う場合はModuleをAppComponentに追加してください。
おわりに
ViewModelProvider
を使うことに思い至らずにby viewModels
を使おうとして詰まってました…本記事を書くにあたって、以下のリンク先を参考にさせて頂きました。
・[Qiita] ViewModelにLiveDataとMutableLiveDataを同時に宣言しなくていいようにする
・[Qiita] Architecture Components を Dagger2 と併用する際の ViewModelProvider.Factory について
- 投稿日:2020-02-28T20:04:19+09:00
【Android】テキスト入力が正しくない時に該当のEditTextにエラーを表示する
概要
例えばログインフォームで情報を入力してログインしようとした時に、メールアドレスやパスワードが間違っていた場合などにエラーメッセージを表示したい。該当項目の入力欄の近くに表示する方法をまとめる。
今回はマテリアルデザインの
TextInputLayout
を用いた。EditTextにエラーを表示する
fragment_form.xml<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.google.android.material.textfield.TextInputLayout android:id="@+id/emailField" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="メールアドレス" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <EditText android:id="@+id/emailText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textEmailAddress"/> </com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputLayout android:id="@+id/passwordField" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="パスワード" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <EditText android:id="@+id/passwordText" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword"/> </com.google.android.material.textfield.TextInputLayout> </LinearLayout>
setError("string")
(もしくはtextInputLayout.error = "string"
)で、エラーを表示させたいEditTextの外側のTextInputLayoutにエラー文をセットする。FormFragment.ktclass FormFragment : Fragment() { private lateinit var emailTextInput: TextInputLayout private lateinit var emailEditText: EditText private lateinit var passwordTextInput: TextInputLayout private lateinit var passwordEditText: EditText ... (略) private fun setError() { if (emailEditText.text.isEmpty()) { emailTextInput.setError("メールアドレスを入力してください") } else if (emailEditText.text.toString() == "a") { emailTextInput.error = "aです" } if (passwordEditText.text.isEmpty()) { passwordTextInput.setError("パスワードを入力してください") } } }これで、該当のEditTextの下にエラーメッセージが表示される。
おまけ: 共通エラー表示
例えば、ログインしようとしてサーバとの通信に失敗した時に「通信に失敗しました」などと表示したいことがある。
その場合はTextView
などを置いて失敗した時にエラーメッセージをセットすれば良い。fragment_form.xml... (略) <TextView android:id="@+id/login_form_error_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#FF0000" /> </LinearLayout>FormFragment.ktprivate fun setError() { if (emailEditText.text.isEmpty() && passwordEditText.text.isEmpty()) { errorText.text = "通信に失敗しました" } else { ... (略)余談: 背景色が緑色なのは、これと同じプロジェクトを使っており、Fragmentの別を分かりやすくするためであるが、今回は別Fragmentは必要なかった。
上記リンクの記事には赤背景と青背景のFragmentが登場する。次何色にしようまとめ
TextInputLayoutを使って、EditTextにエラーを表示した。
- 投稿日:2020-02-28T15:16:53+09:00
51歳からの(現52)プログラミング 備忘 FileOutputStream android.content.Context.openFileOutput(java.lang.String, int)' on a null object reference
こんなコードがあったとする
SampleActivityContext context; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContantView(R.layout.sample); context = getApplicationContext(); new AsyncTask().execute(); } public void fileWrite(){ try{ openFileOutput("fileName",Context.MODE_APPEND); } }AsyncTask@Override protected void onPostExecute(String s){ super.onPostExecute(s); SampleActivity sampleA = new SampleActivity(); sampleA.fileWrite(); }これだと
FileOutputStream android.content.Context.openFileOutput(java.lang.String, int)' on a null object reference
エラーが出る。コード上のインスタンス生成時に
context
が生成されてないので、NullPointerExeption
になる。回避するのなら>https://qiita.com/old_cat/items/ff4f2116192fd536fb59
に記載の通り、コールバックメソッドを実装してcontextを扱う。SampleActivityContext context; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContantView(R.layout.sample); context = getApplicationContext(); AsyncTaskCallBack asyncTaskCallBack = new AsyncTaskCallBack(); AsyncTask asyncTask = new AsyncTask(asyncTaskCallBack); } public class AsyncTaskCallBack(){ // ここでfileOutputStreamを使ってもOK // でも他のメソッドを呼び出してみる public void one(){ fileWriter(); } } public void fileWrite(){ try{ openFileOutput("fileName",Context.MODE_APPEND); } }AsyncTaskSampleActivity.AsyncTaskCallBack asyncTaskCallBack; public AsyncTask(SampleActivity.AsyncTaskCallBack asyncTaskCallBack){ this.asyncTaskCallBack = asyncTaskCallBack; } @Override protected void onPostExecute(String s){ super.onPostExecute(s); asyncTaskCallBack.one(); }
- 投稿日:2020-02-28T14:08:31+09:00
Androidの用語を整理
Androidの基礎用語を整理
Context
・アプリケーションの環境情報とかをグローバル(Android OSの全域)で受け渡しするためのインターフェース
・アクティビティの起動とかブロードキャスト、インテントの受け取りといった他のアプリからの応答を行え、アンドロイド特有のリソース・クラスにアクセスすることも出来る。
アプリ全体の状態を持っていて、何から起動されたかどういう状態か、何にアクセスしようとしているか、といった情報を受け渡すために使っている。ということでいいのかも。(次のサイトから抜粋)
http://individualmemo.blog104.fc2.com/blog-entry-41.htmlActivity
アプリの画面を表示するためのコンポーネントであり、また、状態の保存・復帰のための仕組みや、他のコンポーネントを呼び出す機能がある。ライフサイクルは今後追加
Intent
各コンポーネントの要求をシステムに伝えて、意図にあった相手のコンポーネントとつながる。
Fragment
FragmentはActivity上で運用されるものの独立した機能をもつので,Fragment自身もViewを装備・使用できるが、UI画面としてのライフサイクルの根本をコントロールするのはActivity。紙芝居の土台が、Activityで、一つ一つの紙芝居のページがFragment。ライフサイクルは今後追加
- 投稿日:2020-02-28T13:56:24+09:00
アンドロイド Camera Xとトーチライトの組み合わせ
Camera Xでライトをつける
Camera Xでカメラの機能を実装して、トーチライトを使おうとしたら、あれっ、ライト付かないんだけど...ってなったので今後忘れないようにメモ。
Camera Xの前までやってた書き方↓(※Camera Xではライトが付かなかった例)
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager cameraManager.registerTorchCallback(object: CameraManager.TorchCallback(){ override fun onTorchModeChanged(cameraId: String, enabled: Boolean) { super.onTorchModeChanged(cameraId, enabled) cameraID = cameraId lightOn = enabled } }, Handler()) //トーチライトオン cameraManager.setTorchMode(cameraID, lightOn)これだとCamera 2とかでは使えるけど Camera Xでは使えずトーチライトが付かない。ちなみにビルドは普通に通る。
Camera Xでの書き方↓
前提として、Camera Xのプレビューまで実装済み
val preview = Preview(previewConfig) CameraX.bindToLifecycle(this, preview) //ライトをつける preview.enableTorch(true)まさかのこれだけ。プレビューuse caseを使ってトーチライトをつけることができる。
すごく便利だった。ちなみに自分は最初付かねーってなったけど、ちゃんと公式には書いてあった...
- 投稿日:2020-02-28T13:40:27+09:00
[やってみた][Android]UserLAndを使用したUbuntu環境の構築
環境
LenovoPad 601LV(Softbank TAB3)
Android 6.0
参考サイト
Android で動く Linux 環境 UserLAnd が XServer XSDL に対応
手順
アプリストアより「UserLAnd」「XServer XSDL」をインストール
Android で動く Linux 環境 UserLAnd が XServer XSDL に対応を参考に、UserLAndにUbuntuの環境を構築
XSDL、SSHで接続可能であることを確認
なお、/support/startXSDLServer.shの編集は下記のように行うこと
#! /bin/bash if [[ -z "${INITIAL_USERNAME}" ]]; then INITIAL_USERNAME="user" fi #su $INITIAL_USERNAME -c /support/startXSDLServerStep2.sh sleep 20 su $INITIAL_USERNAME -c 'startlxde &'
- UserLAndの日本語化を参考に、日本語化環境に変更
動作確認
UserLAndからXSDLモードで起動するとLXDEが日本語モードで起動する
PCよりSSH接続
同一ネットワーク上に存在している場合、TerminalやTeraTermからもSSH接続が可能
UserLAndをSSHモードで起動している場合はsshdが起動している。(port 2022)
ssh -p 2022 <UserName>@<IPAddress>