1. ホーム
  2. android

なぜAndroidではビューモデルファクトリーが必要なのか?

2023-09-17 18:08:50

疑問点

これまで議論してきましたが、ビューモデルを直接インスタンス化するのではなく、ビューモデルを作成するファクトリーを作成する理由がわかりません。ビューモデルを作成するだけのファクトリーを作成することの利点は何でしょうか。

ファクトリーなしでやった簡単な例を載せただけです

kodeinのモジュールはこちらです。

val heroesRepositoryModel = Kodein {
    bind<HeroesRepository>() with singleton {
        HeroesRepository()
    }

    bind<ApiDataSource>() with singleton {
        DataModule.create()
    }

    bind<MainViewModel>() with provider {
        MainViewModel()
    }
}

ファクトリーを使わずにビューモデルをインスタンス化するActivityの部分です。

class MainActivity : AppCompatActivity() {
    private lateinit var heroesAdapter: HeroAdapter
    private lateinit var viewModel: MainViewModel
    private val heroesList = mutableListOf<Heroes.MapHero>()
    private var page = 0
    private var progressBarUpdated = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProviders.of(this)
                .get(MainViewModel::class.java)
        initAdapter()
        initObserver()
        findHeroes()
    }

使用例をコンストラクタに入れずに直接インスタンス化したViewModel

class MainViewModel : ViewModel(), CoroutineScope {

    private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
    val data = MutableLiveData<List<Heroes.MapHero>>()

    private var job: Job = Job()
    override val coroutineContext: CoroutineContext
        get() = uiContext + job

    fun getHeroesFromRepository(page: Int) {
        launch {
            try {
                val response = heroesRepository.getHeroes(page).await()
                data.value = response.data.results.map { it.convertToMapHero() }
            } catch (e: HttpException) {
                data.value = null
            } catch (e: Throwable) {
                data.value = null
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

では、ファクトリーを使った例を示します。

class ListFragment : Fragment(), KodeinAware, ContactsAdapter.OnContactListener {

    override val kodein by closestKodein()

    private lateinit var adapterContacts: ContactsAdapter

    private val mainViewModelFactory: MainViewModelFactory by instance()
    private val mainViewModel: MainViewModel by lazy {
        activity?.run {
            ViewModelProviders.of(this, mainViewModelFactory)
                .get(MainViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
    }

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_list, container, false)
    }

viewmodelfactoryです。

class MainViewModelFactory (private val getContacts: GetContacts) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(getContacts) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

そして、ビューモデル。

class MainViewModel(private val getContacts: GetContacts) : BaseViewModel() {
    lateinit var gamesList: LiveData<PagedList<Contact>>
    var contactsSelectedData: MutableLiveData<List<Contact>> = MutableLiveData()
    var contactsSelected: ArrayList<Contact> = ArrayList()
    private val pagedListConfig by lazy {
        PagedList.Config.Builder()
                .setEnablePlaceholders(false)
                .setInitialLoadSizeHint(PAGES_CONTACTS_SIZE)
                .setPageSize(PAGES_CONTACTS_SIZE)
                .setPrefetchDistance(PAGES_CONTACTS_SIZE*2)
                .build()
    }

以下は、完全な最初の例です。

https://github.com/ibanarriolaIT/Marvel/tree/mvvm

そして完全な2番目の例です。

https://github.com/AdrianMeizoso/Payment-App

どのように解決するのですか?

ViewModelを自前で作成することはできません。ViewModelを作成するためには、Androidが提供するViewModelProvidersユーティリティが必要です。

しかし、ViewModelProvidersはargコンストラクタを持たないViewModelsのインスタンス化しかできません。

そのため、複数の引数を持つ ViewModel がある場合、MyViewModel のインスタンスが必要なときに使用するために ViewModelProviders に渡すことができる Factory を使用する必要があります。

例えば

public class MyViewModel extends ViewModel {
    private final MyRepo myrepo;
    public MyViewModel(MyRepo myrepo) {
         this.myrepo = myrepo;
    }
}

この ViewModel のインスタンスを生成するために、ViewModelProvider が使用できるファクトリが必要です。

ViewModelProviders Utility は、コンストラクタにどのように、どのようなオブジェクトを渡せばよいのか分からないため、引数付きコンストラクタで ViewModel のインスタンスを作成することができません。