20200228のAndroidに関する記事は6件です。

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 の引数を持ちます。

MainViewModel
interface MainViewModel {
    ...
}

class MainViewModelImpl @Inject constructor(private val repository: MainRepository) : ViewModel(), MainViewModel {
    ...
}

コンストラクタ付きのViewModelを使うために ViewModelKeyViewModelFactoryを定義します。 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>)
ViewModelFactory
class 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): ViewModel

bindした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を扱えるようになります。

MainFragment
class 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 について

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】テキスト入力が正しくない時に該当のEditTextにエラーを表示する

概要

例えばログインフォームで情報を入力してログインしようとした時に、メールアドレスやパスワードが間違っていた場合などにエラーメッセージを表示したい。該当項目の入力欄の近くに表示する方法をまとめる。
スクリーンショット 2020-02-28 17.56.09.png

今回はマテリアルデザインの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.kt
class 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の下にエラーメッセージが表示される。
スクリーンショット 2020-02-28 18.00.58.png

おまけ: 共通エラー表示

例えば、ログインしようとしてサーバとの通信に失敗した時に「通信に失敗しました」などと表示したいことがある。
その場合は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.kt
    private fun setError() {
        if (emailEditText.text.isEmpty() && passwordEditText.text.isEmpty()) {
            errorText.text = "通信に失敗しました"
        } else {

        ... ()

スクリーンショット 2020-02-28 18.17.03.png

余談: 背景色が緑色なのは、これと同じプロジェクトを使っており、Fragmentの別を分かりやすくするためであるが、今回は別Fragmentは必要なかった。
上記リンクの記事には赤背景と青背景のFragmentが登場する。次何色にしよう

まとめ

TextInputLayoutを使って、EditTextにエラーを表示した。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

51歳からの(現52)プログラミング 備忘 FileOutputStream android.content.Context.openFileOutput(java.lang.String, int)' on a null object reference

こんなコードがあったとする

SampleActivity
Context 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を扱う。

SampleActivity
Context 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);
   }
}
AsyncTask
SampleActivity.AsyncTaskCallBack asyncTaskCallBack;

public AsyncTask(SampleActivity.AsyncTaskCallBack asyncTaskCallBack){
   this.asyncTaskCallBack = asyncTaskCallBack;
}

@Override 
protected void onPostExecute(String s){
   super.onPostExecute(s);
   asyncTaskCallBack.one();
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidの用語を整理

Androidの基礎用語を整理

Context

・アプリケーションの環境情報とかをグローバル(Android OSの全域)で受け渡しするためのインターフェース
・アクティビティの起動とかブロードキャスト、インテントの受け取りといった他のアプリからの応答を行え、アンドロイド特有のリソース・クラスにアクセスすることも出来る。
 アプリ全体の状態を持っていて、何から起動されたかどういう状態か、何にアクセスしようとしているか、といった情報を受け渡すために使っている。ということでいいのかも。(次のサイトから抜粋)
http://individualmemo.blog104.fc2.com/blog-entry-41.html

Activity

アプリの画面を表示するためのコンポーネントであり、また、状態の保存・復帰のための仕組みや、他のコンポーネントを呼び出す機能がある。ライフサイクルは今後追加

Intent

 各コンポーネントの要求をシステムに伝えて、意図にあった相手のコンポーネントとつながる。

Fragment

FragmentはActivity上で運用されるものの独立した機能をもつので,Fragment自身もViewを装備・使用できるが、UI画面としてのライフサイクルの根本をコントロールするのはActivity。紙芝居の土台が、Activityで、一つ一つの紙芝居のページがFragment。ライフサイクルは今後追加

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アンドロイド 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を使ってトーチライトをつけることができる。
すごく便利だった。

ちなみに自分は最初付かねーってなったけど、ちゃんと公式には書いてあった...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[やってみた][Android]UserLAndを使用したUbuntu環境の構築

環境

LenovoPad 601LV(Softbank TAB3)

Android 6.0

参考サイト

Android で動く Linux 環境 UserLAnd が XServer XSDL に対応

UserLAndの日本語化

手順

  1. アプリストアより「UserLAnd」「XServer XSDL」をインストール

  2. Android で動く Linux 環境 UserLAnd が XServer XSDL に対応を参考に、UserLAndにUbuntuの環境を構築

  3. 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 &'
  1. UserLAndの日本語化を参考に、日本語化環境に変更

動作確認

UserLAndからXSDLモードで起動するとLXDEが日本語モードで起動する

PCよりSSH接続

同一ネットワーク上に存在している場合、TerminalやTeraTermからもSSH接続が可能

UserLAndをSSHモードで起動している場合はsshdが起動している。(port 2022)

ssh -p 2022 <UserName>@<IPAddress>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む