KotlinでViewModelを使いこなす12選の方法 – JPSM

KotlinでViewModelを使いこなす12選の方法

Kotlinのコードとロゴが表示され、「ViewModel」というテキストが添えられている画像Kotlin

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

また、理解しにくい説明や難しい問題に躓いても、JPSMがプログラミングの解説に特化してオリジナルにチューニングした画面右下のAIアシスタントに質問していだければ、特殊な問題でも指示に従い解決できるように作ってあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Kotlinは、Android開発をはじめとした多くの場面で使われるモダンなプログラミング言語です。

特にAndroid開発の現場では、ViewModelというコンポーネントと共に非常に効果的に使用されています。

ViewModelは、アプリケーションのデータを管理し、画面回転などの構成変更時にデータを維持する役割を持っています。

この記事では、KotlinでのViewModelの使い方を初心者から中級者向けに12の具体的なサンプルコードとともに解説します。

この記事を最後まで読むことで、KotlinでのViewModelの基本的な使い方から応用技術、そして注意点までしっかりと把握することができるでしょう。

●KotlinとViewModelの基本知識

KotlinとViewModel、この2つのテクノロジーを深く理解することで、アプリケーション開発の幅が格段に広がります。

それでは、それぞれの基本的な知識について詳しく見ていきましょう。

○Kotlinの概要

Kotlinは、JetBrainsによって開発された静的型付けのプログラミング言語です。

Javaとの相互運用性を持つため、Android開発を含む多くのプラットフォームで広く使用されています。

Kotlinは、より簡潔で読みやすいコードを書くことができるため、開発者の生産性の向上に寄与しています。

また、Null安全な設計がなされており、ランタイム時のNull参照によるエラーを大幅に減少させることが可能です。

○ViewModelの役割とメリット

ViewModelは、Androidアーキテクチャコンポーネントの一部として導入されたクラスで、UI関連のデータをライフサイクル意識的に保存・管理する役割を担います。

具体的には、画面回転やバックグラウンド移行など、アクティビティやフラグメントの再作成が発生したときにデータを維持することができます。
ViewModelの主なメリットは次の通りです。

  1. データの維持:構成変更時にデータを失わずに維持することができます。
  2. UIロジックの分離:UIに関するデータをViewModel内で管理することで、ビューとの分離が容易になり、コードの可読性や再利用性が向上します。
  3. テストの容易性:ビューとロジックが分離されているため、単体テストがしやすくなります。

これらのメリットを理解し、ViewModelを効果的に利用することで、アプリケーションの品質や開発速度を大幅に向上させることが期待できます。

●KotlinでのViewModelの基本的な使い方

ここでは、KotlinでのViewModelの基本的な使い方をサンプルコードとともに紹介します。各サンプルコードは実際にアプリ開発で役立つ内容になっています。

○サンプルコード1:ViewModelの基本的な定義方法

ViewModelを利用するには、まずはそれを定義する必要があります。

下記のサンプルコードは、ViewModelの基本的な定義方法を表しています。

import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    // プロパティとメソッドを定義
    var count = 0

    fun incrementCount() {
        count++
    }
}

このコードでは、MyViewModelという名前のViewModelクラスを定義しています。

ViewModel()を継承して、プロパティとメソッドを定義することで、アクティビティやフラグメントとデータを共有できるようになります。

ここでは、countというプロパティと、その値を増やすincrementCountというメソッドを定義しています。

実行すると、MyViewModelクラスのインスタンスを生成し、incrementCountメソッドを呼び出すことで、countプロパティの値が増えるという動作を確認できます。

また、画面回転などでアクティビティが再作成されても、ViewModelのインスタンスは維持されるため、countの値も保持されます。

○サンプルコード2:LiveDataとの連携

LiveDataは、ライフサイクルに安全にデータを保持し、変更を観察するためのクラスです。

下記のコードは、ViewModelとLiveDataの基本的な連携方法を表しています。

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>()
    val count: LiveData<Int> get() = _count

    init {
        _count.value = 0
    }

    fun incrementCount() {
        _count.value = (_count.value ?: 0) + 1
    }
}

このコードでは、_countというMutableLiveDataをプライベートで定義し、その値を外部から取得できるようにcountというLiveDataで公開しています。

incrementCountメソッドが呼ばれると、_countの値が増え、その変更がcountを通じて観察できます。

このパターンを利用すると、ViewModel内でデータの変更を管理しつつ、それを安全に公開することができます。

○サンプルコード3:ViewModelとActivity/Fragmentの連携

ViewModelとActivityやFragmentとの連携も重要です。

下記のサンプルコードは、アクティビティからViewModelを取得し、LiveDataを観察する基本的な例です。

import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer

class MainActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // LiveDataの観察
        viewModel.count.observe(this, Observer { count ->
            // UIの更新
            textView.text = count.toString()
        })
    }
}

このコードでは、viewModels()デリゲートを使って、アクティビティでViewModelのインスタンスを取得しています。

viewModel.count.observeでLiveDataを観察し、データが変更された時にUIを更新しています。

●ViewModelの応用的な使い方

ViewModelはKotlinでのアプリケーション開発において、データとビューの間の連携を助ける重要な役割を果たしています。

基本的な使い方を把握した上で、さらなる高度な利用方法を学ぶことで、アプリの品質や効率を大きく向上させることができます。

○サンプルコード4:複数のLiveDataを取り扱う方法

アプリケーションが成長するにつれ、多くのLiveDataをViewModel内で取り扱う必要が出てきます。

下記のサンプルは、複数のLiveDataを効果的に取り扱う方法を表しています。

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class AdvancedViewModel : ViewModel() {
    private val _name = MutableLiveData<String>()
    private val _age = MutableLiveData<Int>()

    val name: LiveData<String> get() = _name
    val age: LiveData<Int> get() = _age

    fun updateName(newName: String) {
        _name.value = newName
    }

    fun updateAge(newAge: Int) {
        _age.value = newAge
    }
}

このコードでは、_name_ageという2つのMutableLiveDataを定義しています。

それぞれのデータを外部から参照可能にするために、nameageというLiveDataで公開しています。

データの更新は専用のメソッドを通じて行います。

○サンプルコード5:コルーチンとViewModelの連携

Kotlinのコルーチンは非同期処理を効果的に実装するための強力なツールです。

ViewModelとの連携により、UIスレッドとの連携もスムーズになります。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class CoroutineViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            // 非同期でデータを取得
            val data = fetchDataFromServer()
            // UIスレッドでデータを更新
            updateUI(data)
        }
    }
}

上記のコードは、viewModelScopeを利用して非同期処理を実装しています。

viewModelScopeはViewModelのライフサイクルに紐づいているので、ViewModelが破棄されると自動的にキャンセルされます。

○サンプルコード6:ViewModelとRepositoryパターン

Repositoryパターンは、データソースとビジネスロジックの分離を目的としています。

ViewModelとの組み合わせで、アーキテクチャのクリーンさと再利用性を向上させることができます。

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
    fun getUser(userId: Int) = userRepository.getUser(userId)
}

上記のコードでは、UserRepositoryというデータソースをViewModelに注入して利用しています。

●ViewModelのカスタマイズと拡張

Kotlinでのアプリケーション開発では、標準的なViewModelの利用だけでなく、さまざまなカスタマイズや拡張が求められる場面があります。

ここでは、ViewModelを更に柔軟に活用するためのテクニックや、一般的な拡張方法について紹介します。

○サンプルコード7:カスタムViewModelFactoryの利用

通常、ViewModelはViewModelProviderを通じてインスタンス化されます。

しかし、独自の初期化ロジックや依存性の注入が必要な場合、カスタムViewModelFactoryを使用することでこれを実現できます。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class CustomViewModel(val param: String) : ViewModel()

class CustomViewModelFactory(private val customParam: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return CustomViewModel(customParam) as T
    }
}

このコードでは、CustomViewModelが特定のパラメータparamを必要としています。

そのため、CustomViewModelFactoryを作成し、このパラメータを提供することで、必要な初期化を行います。

○サンプルコード8:DIツール(Dagger/Hilt)とViewModel

依存性の注入(DI)は、アプリケーションのスケーラビリティやテスタビリティを向上させるための手法としてよく使用されます。

DaggerやHiltのようなDIツールを使用することで、ViewModelのカスタマイズや拡張が容易になります。

import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class DaggerViewModel @Inject constructor(private val repository: UserRepository) : ViewModel() {
    fun getData() = repository.getData()
}

上記のコードは、Hiltを使用してViewModelをDI対応にした例です。

@HiltViewModelというアノテーションを使用することで、HiltがViewModelのライフサイクルを管理し、必要な依存性を注入します。

●ViewModelの実際の活用例

ViewModelの機能は多岐にわたり、それを最大限に利用することでアプリの品質や開発効率を向上させることができます。

ここでは、実際のアプリ開発でよく遭遇するシチュエーションを元に、ViewModelの具体的な活用方法を紹介します。

○サンプルコード9:ページング処理の実装

多くのアプリでは、大量のデータを効率よく表示するためにページング処理が必要となります。

ViewModelを使用して、ページングをスムーズに実装する方法を見ていきましょう。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn

class PagingViewModel : ViewModel() {

    val flow = Pager(
        PagingConfig(pageSize = 50)
    ) {
        SamplePagingSource()
    }.flow.cachedIn(this)

}

このコードでは、Pagerというクラスを使用してページング処理を行っています。

PagingConfigでページごとのデータ数を指定しています。SamplePagingSourceはデータソースを表すクラスで、実際のデータ取得ロジックを含んでいます。

○サンプルコード10:ネットワーク通信とエラーハンドリング

ViewModelを使用することで、ネットワーク通信やエラーハンドリングも効率よく行うことができます。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class NetworkViewModel : ViewModel() {

    val data = MutableLiveData<String>()
    val error = MutableLiveData<Exception>()

    suspend fun fetchData() {
        try {
            val result = withContext(Dispatchers.IO) {
                // ここでAPI通信などの処理を行う
                "データ取得成功"
            }
            data.postValue(result)
        } catch (e: Exception) {
            error.postValue(e)
        }
    }
}

このコードは、APIからデータを取得し、成功時にはdataに、失敗時にはerrorに結果を保存します。

withContextを使用することで非同期での通信を行っています。

○サンプルコード11:状態管理とUIの動的変更

ViewModelはアプリケーションのロジックやデータを管理する役割を持つため、UIの状態管理にも大いに役立ちます。

この部分では、アプリのUIの状態を変更するためのViewModelの活用例を具体的に見ていきます。

まず、アプリのロード中、データの取得成功、データの取得失敗といった3つの状態を想定します。

これらの状態をEnumで管理します。

enum class UIState {
    LOADING,
    SUCCESS,
    ERROR
}

次に、上記の状態をLiveDataで管理するViewModelを定義します。

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class StateViewModel : ViewModel() {

    val uiState: MutableLiveData<UIState> = MutableLiveData(UIState.LOADING)

    fun fetchData() {
        // データの取得処理
        // 成功時: uiState.value = UIState.SUCCESS
        // 失敗時: uiState.value = UIState.ERROR
    }
}

このコードでは、LiveDataを使ってUIの状態を保持しています。

データを取得するfetchDataメソッド内で、成功時や失敗時にその状態を更新しています。

最後に、このViewModelを利用して、ActivityやFragmentでUIを動的に変更する例を見てみましょう。

uiState.observe(viewLifecycleOwner, Observer { state ->
    when (state) {
        UIState.LOADING -> {
            // ローディングの表示
        }
        UIState.SUCCESS -> {
            // データの表示
        }
        UIState.ERROR -> {
            // エラーメッセージの表示
        }
    }
})

上記のコードを利用することで、状態に応じたUIの変更を柔軟に行うことができます。

LiveDataの監視を行うことで、データの変更があった際にUIを自動的に更新することができ、コードの簡潔性や可読性も向上します。

○サンプルコード12:ViewModelのテスト方法

アプリケーションの品質を保つためには、テストの実施は欠かせません。

ViewModelもテストの対象となります。

ここでは、ViewModelの単体テスト方法を解説します。

import org.junit.Test
import org.junit.Before
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

class StateViewModelTest {

    private lateinit var viewModel: StateViewModel

    @Before
    fun setUp() {
        viewModel = StateViewModel()
    }

    @Test
    fun testFetchData_success() {
        // Mockのデータソースやリポジトリを使用して、データの取得成功をシミュレート
        viewModel.fetchData()

        // 成功時の状態が設定されているかを確認
        assertEquals(UIState.SUCCESS, viewModel.uiState.value)
    }

    @Test
    fun testFetchData_error() {
        // Mockのデータソースやリポジトリを使用して、データの取得失敗をシミュレート
        viewModel.fetchData()

        // エラー時の状態が設定されているかを確認
        assertEquals(UIState.ERROR, viewModel.uiState.value)
    }
}

このテストコードは、ViewModelのfetchDataメソッドが正しく状態を更新しているかを確認するものです。

Mockitoというライブラリを使用して、外部のデータソースやリポジトリの動作を模擬することで、ViewModelのロジックのみを対象としたテストを行っています。

●ViewModelを利用する際の注意点とその対処法

ViewModelをKotlinで使用するときに注意すべきポイントや対処方法を紹介します。

○UIに直接関連しないデータの管理

ViewModelの主要な目的はUIに関連するデータのライフサイクルを管理することです。

しかし、ViewModel内でUIに直接関連しないデータやロジックを持つことは推奨されません。

これはViewModelの役割から外れるため、可読性や保守性が低下する可能性があります。

対処法として、UIに関連しないデータやロジックは、リポジトリや別の管理クラスに分離しましょう。

// 望ましくない例
class MyViewModel : ViewModel() {
    fun fetchDataFromNetwork() {
        // ネットワークからのデータ取得
    }
}

// 望ましい例
class MyRepository {
    fun fetchDataFromNetwork() {
        // ネットワークからのデータ取得
    }
}

class MyViewModel(private val repository: MyRepository) : ViewModel() {
    fun fetchData() {
        repository.fetchDataFromNetwork()
    }
}

このコードを実行すると、ViewModelはリポジトリを介してデータを取得します。

この構造は、ロジックとデータの取得を適切に分離しているため、テストや保守が容易になります。

○ViewModelの過剰な依存

ViewModelが多くの依存関係を持つ場合、テストや再利用が困難になる可能性があります。

対処法として、DI(Dependency Injection)ツールを使用して、ViewModelの依存関係を外部から注入することを検討しましょう。

例として、HiltやKoinなどのライブラリがあります。

○メモリリークのリスク

ViewModelがContextやViewの参照を持っている場合、メモリリークのリスクが高まります。

対処法として、ViewModel内でのContextやViewの直接的な参照は避け、必要な場合は弱参照を使用するか、それらのオブジェクトのライフサイクルに注意を払いましょう。

○LiveDataの不適切な使用

LiveDataの更新は主にUIスレッドで行われますが、重い処理をUIスレッドで行うとアプリのパフォーマンスが低下する可能性があります。

対処法として、重い処理はバックグラウンドスレッドで行い、結果のみをLiveDataで通知するようにしましょう。

Kotlinのコルーチンを使用してバックグラウンド処理を行うと、効率的に実装できます。

●KotlinでのViewModelの最適化とパフォーマンス向上のためのヒント

ViewModelの効果的な使用方法をマスターすることで、アプリのパフォーマンスとユーザー体験を向上させることができます。

ここでは、Kotlinを使用したViewModelの最適化とパフォーマンス向上のためのヒントをいくつか紹介します。

○LiveDataの適切な更新頻度

LiveDataの更新頻度が高すぎると、UIの再描画が頻繁に発生し、アプリのパフォーマンスが低下する恐れがあります。

対処法として、必要なタイミングでのみLiveDataを更新し、不要な更新を避けるようにしましょう。

例えば、データが実際に変更されたときだけ更新するようにします。

class MyViewModel : ViewModel() {
    val data: MutableLiveData<String> = MutableLiveData()

    fun updateData(newData: String) {
        if (data.value != newData) {
            data.value = newData
        }
    }
}

このコードでは、既存のデータと新しいデータが異なる場合のみLiveDataを更新します。

これにより、不必要なUIの再描画を防ぐことができます。

○コルーチンの適切な使用

Kotlinのコルーチンは非同期処理を簡単に書ける強力なツールですが、不適切に使用するとリソースの浪費やアプリのクラッシュを引き起こす可能性があります。

対処法として、コルーチンを使用する際は、適切なスコープやディスパッチャを指定して実行しましょう。

ViewModelの場合、viewModelScopeを使用するのが一般的です。

class MyViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            // データ取得などの非同期処理
        }
    }
}

viewModelScopeを使用することで、ViewModelがクリアされるときに自動的にコルーチンがキャンセルされます。

これにより、メモリリークを防ぐことができます。

○適切なデータ構造の選択

ViewModel内で使用するデータ構造の選択も、アプリのパフォーマンスに影響を与える重要なポイントです。

対処法として、大量のデータを持つリストやマップを操作する場合、適切なデータ構造を選択し、不要な再計算やメモリの無駄遣いを避けるようにしましょう。

まとめ

KotlinでのViewModelの使用方法は、アプリケーション開発において非常に効果的な手段となります。

この記事を通して、ViewModelの基本的な使い方から応用例、さらには最適化やパフォーマンス向上のヒントまで幅広く紹介しました。

特に、LiveDataの適切な更新頻度やコルーチンの正しい使用方法、データ構造の選択など、パフォーマンスに直接影響するポイントについての理解を深めることができたことと思います。

KotlinとViewModelを組み合わせることで、効率的かつ安全なアプリケーション開発が実現します。

初心者から中級者まで、この記事の内容を理解し適用することで、アプリの品質とパフォーマンスの向上を実感できることでしょう。

日々の開発において、今回学んだ知識を活かし、更なるスキルアップを目指してください。