The proper way is to use a dependency injection framework such as Dagger hilt. If not using a DI framework, then do it with ViewModelFactory.
With Dagger Hilt:
A ViewModel with parameters
@HiltViewModel
class MyViewModel @Inject constructor(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }
A Repository
class MyRepository @Inject constructor(
private val myRemoteDataSource: MyDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) { ... }
A Module for providing the dependencies/parameters so they can be injected into repositories and ViewModels.
@InstallIn(ViewModelComponent::class)
@Module
object MyProvideModule {
@Provides
fun provideMyDataSource(@ApplicationContext context: Context): MyDataSource {
//code to create MyDataSource...
return MyDataSource(context)
}
@Provides
fun provideCoroutineDispatcher(): CoroutineDispatcher {
return Dispatchers.IO
}
}
A module for binding the repository
@Module
@InstallIn(ViewModelComponent::class)
interface RepositoryModules {
@Binds
fun provideMyRepository(repository: MyRepository): MyRepository
}
Initiating Dagger hilt with the application with the @HiltAndroidApp annotation.
@HiltAndroidApp
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
Getting the ViewModel in activities
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
// Other code...
}
Getting the ViewModel in fragments
@AndroidEntryPoint
class MyFragment : Fragment() {
private val myViewModel: MyViewModel by activityViewModels()
// Other code...
}
With ViewModelFactory:
A ViewModel with parameter messageDataStore, where MessageDataStore is a DataStore class or it can be anything else that you want to pass into the ViewModel.
class MyViewModel(
private val messageDataStore: MessageDataStore,
): ViewModel() { ... }
The ViewModel factory class for creating ViewModels
/**
* Factory for all ViewModels.
*/
@Suppress("UNCHECKED_CAST")
class ViewModelFactory constructor(
private val messageDataStore: MessageDataStore,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
) = with(modelClass) {
when {
isAssignableFrom(MyViewModel::class.java) ->
MyViewModel(messageDataStore)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
The application class for creating the dependencies/parameters
class MyApp : Application() {
val messageDataStore: MessageDataStore
get() = MessageDataStore.getInstance(this)
}
Extension functions for getting the factory class in activities and fragments, MyExt.kt
fun AppCompatActivity.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this, savedInstanceState)
}
fun Fragment.getViewModelFactory(savedInstanceState: Bundle?): ViewModelFactory {
val messageDataStore = (requireContext().applicationContext as MyApp).messageDataStore
return ViewModelFactory(messageDataStore, this.requireActivity(), savedInstanceState)
}
Getting the ViewMode in activities
class MainActivity : AppCompatActivity() {
private lateinit var myViewModel: MyViewModel
// Other code...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm by viewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
// Other code...
}
}
Getting the ViewModel in Fragments.
class MyFragment : Fragment() {
private lateinit var myViewModel: MyViewModel
//Other code...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val vm by activityViewModels<MyViewModel> { getViewModelFactory(savedInstanceState) }
myViewModel = vm
//Other code...
}
}