diff --git a/app/src/main/java/com/hossainkhan/android/demo/di/ApplicationModule.kt b/app/src/main/java/com/hossainkhan/android/demo/di/ApplicationModule.kt index f702a1b..c7ab018 100644 --- a/app/src/main/java/com/hossainkhan/android/demo/di/ApplicationModule.kt +++ b/app/src/main/java/com/hossainkhan/android/demo/di/ApplicationModule.kt @@ -30,7 +30,7 @@ import dagger.Module * * @see DemoApplicationComponent */ -@Module +@Module(includes = [ViewModelModule::class]) abstract class ApplicationModule { //expose Application as an injectable context @Binds diff --git a/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelKey.kt b/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelKey.kt new file mode 100755 index 0000000..e5a6c91 --- /dev/null +++ b/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelKey.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Hossain Khan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hossainkhan.android.demo.di + +import androidx.lifecycle.ViewModel +import dagger.MapKey +import kotlin.reflect.KClass + +/** + * See https://dagger.dev/multibindings.html + */ +@MustBeDocumented +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER +) +@Retention(AnnotationRetention.RUNTIME) +@MapKey +annotation class ViewModelKey(val value: KClass) diff --git a/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelModule.kt b/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelModule.kt new file mode 100755 index 0000000..ff1e583 --- /dev/null +++ b/app/src/main/java/com/hossainkhan/android/demo/di/ViewModelModule.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Hossain Khan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hossainkhan.android.demo.di + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.hossainkhan.android.demo.ui.browse.LayoutBrowseViewModel +import com.hossainkhan.android.demo.ui.layoutpreview.LayoutInfoViewModel +import com.hossainkhan.android.demo.viewmodel.ViewModelProviderFactory +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap + +/** + * Uses dagger multi-binding to provide [ViewModel] instances used in the app. + * + * @see Multibindings + */ +@Suppress("unused") +@Module +abstract class ViewModelModule { + @Binds + @IntoMap + @ViewModelKey(LayoutBrowseViewModel::class) + abstract fun bindLayoutBrowserViewModel(layoutBrowseViewModel: LayoutBrowseViewModel): ViewModel + + @Binds + @IntoMap + @ViewModelKey(LayoutInfoViewModel::class) + abstract fun bindLayoutInfoViewModel(layoutInfoViewModel: LayoutInfoViewModel): ViewModel + + @Binds + abstract fun bindViewModelFactory(factory: ViewModelProviderFactory): ViewModelProvider.Factory +} diff --git a/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseActivity.kt b/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseActivity.kt index 6e37f74..d739515 100644 --- a/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseActivity.kt +++ b/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseActivity.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.hossainkhan.android.demo.R -import com.hossainkhan.android.demo.viewmodel.LayoutPreviewViewModelFactory +import com.hossainkhan.android.demo.viewmodel.ViewModelProviderFactory import dagger.android.AndroidInjection import javax.inject.Inject @@ -31,7 +31,7 @@ import javax.inject.Inject */ class LayoutBrowseActivity : AppCompatActivity() { @Inject - internal lateinit var viewModelFactory: LayoutPreviewViewModelFactory + internal lateinit var viewModelFactory: ViewModelProviderFactory private lateinit var recyclerView: RecyclerView private lateinit var viewAdapter: RecyclerView.Adapter<*> diff --git a/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseViewModel.kt b/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseViewModel.kt index ae96860..51de09b 100644 --- a/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseViewModel.kt +++ b/app/src/main/java/com/hossainkhan/android/demo/ui/browse/LayoutBrowseViewModel.kt @@ -30,8 +30,9 @@ import com.hossainkhan.android.demo.ui.layoutpreview.LayoutGuidelineBarrierActiv import com.hossainkhan.android.demo.ui.layoutpreview.LayoutGuidelineGroupActivity import com.hossainkhan.android.demo.ui.layoutpreview.LayoutVisibilityGoneActivity import timber.log.Timber +import javax.inject.Inject -class LayoutBrowseViewModel( +class LayoutBrowseViewModel @Inject constructor( appDataStore: AppDataStore, private val browseNavigator: LayoutBrowseNavigator) : ViewModel() { diff --git a/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutInfoViewModel.kt b/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutInfoViewModel.kt index 9cab1c4..4202fdd 100644 --- a/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutInfoViewModel.kt +++ b/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutInfoViewModel.kt @@ -23,11 +23,12 @@ import androidx.annotation.LayoutRes import com.hossainkhan.android.demo.data.AppDataStore import com.hossainkhan.android.demo.data.LayoutInformation import timber.log.Timber +import javax.inject.Inject /** * ViewModel for containing layout information. */ -class LayoutInfoViewModel(private val appDataStore: AppDataStore) : ViewModel() { +class LayoutInfoViewModel @Inject constructor(private val appDataStore: AppDataStore) : ViewModel() { private val layoutInfoLiveData: MutableLiveData = MutableLiveData() val layoutInformation: LiveData diff --git a/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutPreviewBaseActivity.kt b/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutPreviewBaseActivity.kt index 531abb5..524c9b6 100644 --- a/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutPreviewBaseActivity.kt +++ b/app/src/main/java/com/hossainkhan/android/demo/ui/layoutpreview/LayoutPreviewBaseActivity.kt @@ -16,22 +16,20 @@ package com.hossainkhan.android.demo.ui.layoutpreview -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle -import androidx.annotation.LayoutRes -import androidx.browser.customtabs.CustomTabsIntent -import androidx.core.app.NavUtils -import androidx.appcompat.app.AppCompatActivity import android.view.Menu import android.view.MenuItem +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NavUtils +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider import com.hossainkhan.android.demo.R import com.hossainkhan.android.demo.data.LayoutInformation import com.hossainkhan.android.demo.ui.dialog.LayoutInfoDialog -import com.hossainkhan.android.demo.viewmodel.LayoutPreviewViewModelFactory +import com.hossainkhan.android.demo.viewmodel.ViewModelProviderFactory import dagger.android.AndroidInjection import timber.log.Timber import javax.inject.Inject @@ -77,7 +75,7 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() { } @Inject - internal lateinit var viewModelFactory: LayoutPreviewViewModelFactory + internal lateinit var viewModelFactory: ViewModelProviderFactory private lateinit var viewModel: LayoutInfoViewModel @@ -123,9 +121,7 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() { Timber.d("Layout info is showing: %s", infoDialog?.isVisible) if (infoDialog?.isVisible == false) { if (fromUser || viewModel.isFirstTime) { - infoDialog?.let { - it.show(supportFragmentManager, "dialog") - } + infoDialog?.show(supportFragmentManager, "dialog") } } else { infoDialog?.dismiss() diff --git a/app/src/main/java/com/hossainkhan/android/demo/viewmodel/LayoutPreviewViewModelFactory.kt b/app/src/main/java/com/hossainkhan/android/demo/viewmodel/LayoutPreviewViewModelFactory.kt deleted file mode 100644 index 3edad48..0000000 --- a/app/src/main/java/com/hossainkhan/android/demo/viewmodel/LayoutPreviewViewModelFactory.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018 Hossain Khan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hossainkhan.android.demo.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.hossainkhan.android.demo.ui.browse.LayoutBrowseNavigator -import com.hossainkhan.android.demo.ui.browse.LayoutBrowseViewModel -import com.hossainkhan.android.demo.data.AppDataStore -import com.hossainkhan.android.demo.ui.layoutpreview.LayoutInfoViewModel -import javax.inject.Inject - -/** - * The [ViewModelProvider.Factory] that provides all the ViewModels for the activities and fragments. - */ -class LayoutPreviewViewModelFactory @Inject constructor( - private val dataStore: AppDataStore, - private val browseNavigator: LayoutBrowseNavigator - -) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return when { - modelClass.isAssignableFrom(LayoutInfoViewModel::class.java) -> { - LayoutInfoViewModel(dataStore) as T - } - modelClass.isAssignableFrom(LayoutBrowseViewModel::class.java) -> { - LayoutBrowseViewModel(dataStore,browseNavigator) as T - } - else -> throw IllegalArgumentException("Unknown ViewModel class") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/hossainkhan/android/demo/viewmodel/ViewModelProviderFactory.kt b/app/src/main/java/com/hossainkhan/android/demo/viewmodel/ViewModelProviderFactory.kt new file mode 100644 index 0000000..ffba3ec --- /dev/null +++ b/app/src/main/java/com/hossainkhan/android/demo/viewmodel/ViewModelProviderFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Hossain Khan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hossainkhan.android.demo.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton +import com.hossainkhan.android.demo.di.ViewModelModule +/** + * The [ViewModelProvider.Factory] to get instance of all viewmodels using Dagger multi-binding. + * + * + * @see Multibindings + * @see [ViewModelModule] + */ +@Singleton +class ViewModelProviderFactory @Inject constructor( + private val creators: Map, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + val creator = creators[modelClass] ?: creators.entries.firstOrNull { + modelClass.isAssignableFrom(it.key) + }?.value ?: throw IllegalArgumentException("Unknown view model class $modelClass. Must be added to map first.") + try { + @Suppress("UNCHECKED_CAST") + return creator.get() as T + } catch (e: Exception) { + throw RuntimeException(e) + } + } +} \ No newline at end of file