プログラマになる

組み込みエンジニアで主にC言語、時々C#をやっている人の技術メモです。最近はAndroidも少しずつ勉強中

ViewModelを生成する際にModelを指定する

はじめに

ViewModelProvidersを通してViewModelを生成する場合、 ModelをViewModelに受け渡すには一工夫必要みたいです。 本記事ではModelをViewModelに渡すための実装方法を記載します。

実装

ViewModelProviders.createの呼び出し時にViewModelProvider.Factoryクラスを指定することで ViewModelにModelをDIすることができるようになります。

1.ViewModelを作成する

ViewModelがModelをDIできるようにコンストラクタのパラメータにModelを追加します。

class TextToSpeechDemoViewModel(tts : TextToSpeech) : ViewModel() {
    val tts : TextToSpeech = tts
}

2.ViewModelFactoryを作成する

ViewModelProviders.Factoryを継承し、ModelをViewModelにDIするように実装します。

class ViewModelFactory(tts : TextToSpeech) : ViewModelProvider.Factory{
    val tts : TextToSpeech = tts

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {

        if (modelClass == TextToSpeechDemoViewModel::class.java)
            return TextToSpeechDemoViewModel(tts) as T

        throw IllegalArgumentException("Unknown ViewModel class : ${modelClass.name}")
    }
}

3.ViewModelFactoryをViewModelProvidersにセットしViewModelを生成する

2で作成したViewModelFactoryをViewModelProviers.ofに指定しViewModelを生成します。

class TextToSpeechDemoActivity : AppCompatActivity() {
    var tts : TextToSpeech? = null
    var viewModel : TextToSpeechDemoViewModel? = null
    var viewModelFactory : ViewModelFactory? = null
    
    fun onCreate(savedInstanceState: Bundle?) {
        val tts = android.speech.tts.TextToSpeech(applicationContext, object : TextToSpeech.OnInitListener{
            override fun onInit(p0: Int) {
                if(p0 != android.speech.tts.TextToSpeech.ERROR)
                    tts?.language = Locale.JAPANESE
            }
        })

        viewModelFactory = ViewModelFactory(tts as TextToSpeech)
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(TextToSpeechDemoViewModel::class.java)
    }
}

おわりに

ViewModelProviersからViewModelを生成する場合にModelをDIする方法を載せました。
Modelが多くなった場合にはこの別の実装が必要になるかもしれませんが小規模ならばこれで十分だと思います。

参考記事

https://medium.com/@dpreussler/add-the-new-viewmodel-to-your-mvvm-36bfea86b159

C# XMLファイルの書き込みと読み込み

はじめに

C#XMLファイルの書き込み・読み込みを利用するには下記のクラスを利用する。

名称 説明
XmlWriter XML形式でファイルに書き込む
XmlTextWriter おそらく非推奨でXmlWriterを利用したほうがよい
XmlReader XML形式のファイルを読み込む

サンプルコード

上記のクラスを使ったXMLファイルの書き込みと読み込みのサンプルです。
※書き込むクラスのアクセシビリティはpublicでなければならないので注意が必要です。

public class Person
{
  public string Name {get; set;}
  public string Age { get; set; }
}

public void Save()
{

    var setting = new XmlWriterSettings();
    setting.Indent = true;

    using (var writer = XmlWriter.Create(@"app.xml", setting))
    {
        var serializer = new XmlSerializer(typeof(Person));
        var person = new Person();
        serializer.Serialize(writer, person);
    }
}

public void Load()
{
    using (var reader = XmlReader.Create(@"app.xml"))
    {
        var serializer = new XmlSerializer(typeof(Person));
        var person = serializer.Deserialize(reader);
    }
}

インデント付きで書き込むには?

XmlWriterSettings.Indentをtrueに設定し、
XmlWriterのインスタンスを生成する際に指定します。

    var setting = new XmlWriterSettings();
    setting.Indent = true;
    var writer = XmlWriter.Create(@"app.xml", setting)

コレクションを書き込むには?

内部に配列を持つコレクションクラスを作成します。

  [XmlRoot("Persons")]
  public class PersonCollection
  {
      [XmlElement(Type = typeof(Person), ElementName = "Person")]
      public Person[] Persons { get; set; }
  }
  
  var serializer = new XmlSerializer(typeof(Persons));
  var persons = new Persons();
  serializer.Serialize(writer, persons);

学習中「Android アプリ設計パターン入門」を読んで学ぶMVVMアーキテクチャ

はじめに

「Android アプリ設計パターン」
「第2章 MVVMパターンを使ったアプリ構成」から学んだことをまとめます。

Androidアプリ設計パターン」では「Android Architecture Blueprints」
TODOアプリを例にMVVMパターンの解説しています。

私はKotlinの勉強も兼ねて「Android Architecture Blueprints」の
「dev-todo-mvvm-live-kotlin」リポジトリを閲覧しました。

そのため本稿に記載するソースコード
「dev-todo-mvvm-live-kotlin」リポジトリのものになります。

peaks.cc

github.com

学んだこと

以下に記載するのがAndroidMVVMパターン
構築するための基本要素です。
これらの各役割をざっくり記載します。

  • Model
  • ViewModel
  • Navigator
  • Activity
  • Fragment

Model

データベースへのアクセスやAPIの呼び出しなど、
アプリケーションのロジックをModelに定義する。

ViewModel

例えば詳細表示に必要なタスクの状態、タイトル・概要の読み込みを行う。
情報の引き出しにはコンストラクト時に渡されるModelを利用する。

Navigator

Navigatorにアクションのインタフェースを定義する。
Navigatorはインタフェースであるため
利用するには実装済みのインスタンスが必要となる。
アクションなので画面遷移などに関連する処理をNavigatorに定義するみたい。

Fragment

Viewを所持し、UIの振る舞いを保証する。
ユーザーがコンポーネントを操作したときの動作を決めたり、
View ↔ ViewModelのデータバインディング設定を行い表示する情報を決める

Activity

全オブジェクトのライフサイクルを管理する。
主ににModel・ViewModel・Navigatorのインスタンスを作成し管理する。

ModelとViewModelの作成

ViewModelの生成方法はViewModelFactory.ktに定義されている。
ここでViewModelごとにどのModelを作成しInjectionするか決めているっぽい。 ActivityからViewModelを作成するときにはAppCompatActivityExt.ktのobtainViewModelを利用して必要なViewModelを生成している。

呼び出し1 TaskDetailActivity.kt
fun obtainViewModel(): TaskDetailViewModel = obtainViewModel(TaskDetailViewModel::class.java)
呼び出し2 AppCompatActivityExt.kt
fun <T : ViewModel> AppCompatActivity.obtainViewModel(viewModelClass: Class<T>) =
        ViewModelProviders.of(this, ViewModelFactory.getInstance(application)).get(viewModelClass)
呼び出し3 ViewModelFactory.kt
fun getInstance(application: Application) =
                INSTANCE ?: synchronized(ViewModelFactory::class.java) {
                    INSTANCE ?: ViewModelFactory(application,
                            Injection.provideTasksRepository(application.applicationContext))
                            .also { INSTANCE = it }
                }

Navigatorの作成

Navigatorの作成というよりはNavigatorの実装になる。
ActivityにNavigatorを実装しViewModelのCommandと紐付ける。

interface TaskDetailNavigator {

    fun onTaskDeleted()

    fun onStartEditTask()
}

class TaskDetailActivity : AppCompatActivity(), TaskDetailNavigator {
    ︙
    override fun onCreate(savedInstanceState: Bundle?) {
        ︙
        taskViewModel = obtainViewModel()
        subscribeToNavigationChanges(taskViewModel)
    }
  
    private fun subscribeToNavigationChanges(viewModel: TaskDetailViewModel) {
        val activity = this@TaskDetailActivity
        viewModel.run {
            editTaskCommand.observe(activity,
                    Observer { activity.onStartEditTask() })
            deleteTaskCommand.observe(activity,
                    Observer { activity.onTaskDeleted() })
        }
    }
  
    override fun onTaskDeleted() {
        ︙
    }

    override fun onStartEditTask() { 
        ︙
    }

まとめ

Android MVVMパターンは各クラスの役割を理解することが大切!!

  • Modelにはデータ取得などのロジックを書く
  • ViewModelはユーザの操作やFragment、Activityの処理の橋渡し
  • Navigatorはアクションのインタフェースを決める
  • ActivityはModel・ViewModel・Navigatorのインスタンスを生成・管理する
  • FragmentはViewを持ち、コンポーネントの表示・振る舞いを決める

レポジトリパターン: Repository Pattern

レポジトリパターン : Repository Patternとは

データ層へアクセスするコードをレポジトリに記述する。
レポジトリに複雑なデータ取得を
ロジックを集約して管理するパターンらしい。 

データ層へのアクセスをレポジトリに
任せることでビジネスロジックの抽象度が上がり、
ビジネスロジックがより明確になり保守性が向上するとのこと。

またレポジトリを採用することで、
他クラスで単体テストが書きやすくなるなどメリットが多い。
主にデータベース・WEBサービス
アクセスする処理をリポジトリに記述する。

参考記事

プロセス監視ツール(Ressurection)を作った

概要

プロセス監視ツール 「Ressurection」を作りました。
「Ressurection」は登録したソフトウェアのプロセスを監視し、
クラッシュしたら再起動するという機能を持つソフトウェアです。

使い方

使い方は説明するまでもないくらい簡単にしたつもりです。
アプリケーションを起動するとタスクトレイに表示されます。
右クリックで、「Show Setting」で設定画面を開きます。

f:id:kaleidot725:20180403222007p:plain

設定画面で監視するソフトウェアの追加と削除ができます。
+ボタンを押しソフトウェアを追加し
プロセス名称のトグルボタンを押して起動するだけです。
-ボタンを押すとプロセスを削除できます。

f:id:kaleidot725:20180403222012p:plain

登録したプロセスは設定ファイル(setting.json)に保存されます。

[
  {
    "Path": "C:\\Python27\\python.exe"
  }
]

作ってみて

  • 自分が使い慣れているC#WPFで作ったので技術的な苦労はなかった。
  • そもそもGUIアプリケーションを作りたいわけではなく
    テスト駆動開発の練習ネタとして作成した。
  • しかし肝心なテスト駆動開発は主要クラスだけで、
    後はテスト書いてないという状況になってしまった。
  • 今後はViewModelとかも含めてテストコード書ければ良いと思う。
    時間的に余裕ができたり実用性が出てきたらテストを追加したい。

実行ファイルはこちら github.com

最も単純なViewModelとLiveDataの利用

セットアップ

Googleのレポジトリを追加する

buildscriptのrepositoriesにgoogle()を追加する。
以下が設定例です。

buildscript {
    ext.kotlin_version = '1.2.21'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Android Architecture Componentを追加する

以下のリストから追加したいAndroid Architecture Componentを選択する

implementation "android.arch.lifecycle:extensions:1.1.1"                  // ViewModel and LiveData
implementation "android.arch.lifecycle:viewmodel:1.1.1"                  // alternatively, just ViewModel
implementation "android.arch.lifecycle:livedata:1.1.1"                       // alternatively, just LiveData
implementation "android.arch.persistence.room:runtime:1.0.0"      // Room
implementation "android.arch.paging:runtime:1.0.0-alpha7"           // Paging
testImplementation "android.arch.core:core-testing:1.1.1"               // Test helpers for LiveData
testImplementation "android.arch.persistence.room:testing:1.0.0" // Test helpers for Room

以下が設定例です。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "kaleidot725.classicclock"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation "android.arch.lifecycle:extensions:1.1.1"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

ViewModelを作成する

今回はLocalDateTimeを1msおきに通知するLiveDataを含んだViewModelを作成する。

class ClockViewModel : ViewModel {
    public val mLocalDateTime: MutableLiveData<LocalDateTime> = MutableLiveData()
    val mTimer : Timer = Timer()

    constructor() {
        mTimer.schedule(object : TimerTask() {
           override  fun run() {
                mLocalDateTime.postValue(LocalDateTime.now())
           }
        }, 1, 1)
    }
}

ActivityにてViewModelを生成する

ViewModelProviders.ofにてViewModelのインスタンスを生成する。
これでViewModelProvidersがViewModelのライフサイクルを
管理してくれるので画面を回転させても問題なくなる。

class MainActivity : AppCompatActivity() {
    var mClockViewModel : ClockViewModel? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mClockViewModel = ViewModelProviders.of(this).get(ClockViewModel::class.java)
    }
}

ViewModelのLiveDataを購読する

ViewModelのLiveDataを購読する。

    mClockViewModel?.mLocalDateTime?.observe(this, object : Observer<LocalDateTime>{
        override fun onChanged(t: LocalDateTime?) {
            val clockTextView = findViewById<TextView>(R.id.ClockText)
            clockTextView.text = t.toString()
        }
    })
}

実行結果

f:id:kaleidot725:20180329235038p:plain

github.com

「Kotlin入門までの助走読本」を読んだ

はじめに

日本Kotlinユーザグループが提供している資料で、
Kotlinとは何かを簡潔に説明しているらしいです。

「Kotlin入門までの助走読本」

読んでみて

写経しながら進めましたが、簡潔でわかりやすかったので2~3日で読み終わりました。
(写経したソースコードこちらです。)

サンプルコードも充実しているので
これがあれば基本的な機能は網羅できると思います。

自身の力不足もあると思うのですが、
ラムダ式の解説だけ?だったのでkotlinでのラムダ式・メソッド渡しを参考にしました。

またこの資料は単純にKotlinの言語仕様だけを述べているのではなく、
JavaからKotlinへの移行」など言語を変える時に問題視される事柄について記載があります。
こういった実際の運用に関わる事柄を日本語で読めるのはありがたいなと思いました。

これから

「Kotlinって良い言語かも?」と思ったので 引き続き学習を進めたいと思っています。
手始めにAndroidアプリケーションをKotlinで作り始めるのもありかなと思っています。

勢いでKotlinイン・アクション 」と「テスト駆動開発」を買ったので、
こいつらを活かして何かを作れれば最高かなと思います。