i have a problem.
I try pass a variable <ArrayList<List>> betwenne two ViewModels.
ViewModel A:
#HiltViewModel
class RouteTrackViewModel #Inject constructor(
application: Application
) : ViewModel(){
private val locationLiveData = LocationLiveData(application)
private var finalRoute = ArrayList<List<Double>>()
private var finalTime : Duration = Duration.ZERO
fun getLocationLiveData() = locationLiveData
fun getFinalRoute() = finalRoute
...
}
ViewModel B:
#HiltViewModel
class RouteSaveViewModel #Inject constructor(
private var routeTrackViewModel : RouteTrackViewModel
) : ViewModel() {
fun getLocationLiveData() = routeTrackViewModel.getLocationLiveData()
fun getRouteLocation() = routeTrackViewModel.getFinalRoute()
}
if i use like that give me this error:
Injection of an #HiltViewModel class is prohibited since it does not create a ViewModel instance correctly.
Access the ViewModel via the Android APIs (e.g. ViewModelProvider) instead.
Injected ViewModel: com.xxxxxxxxxxxxxx.xxxxxxxxxxxx.presentation.routes.route_track.RouteTrackViewModel
please can you help me to fine a good solution?
I expect to have access to final Route in Route Save ViewModel.
I need these values to show on Mapbox a polyline and create a form to save the route later in a JSON file and send it to my database.
Related
I use Hilt as DI in an Android Studio project, and viewModel() will create an instance of SoundViewModel automatically.
Code A works well.
I think viewModel() will create an Singleton of SoundViewModel.
I think mViewMode_A will be assigned to mViewMode_B automatically without creating a new instance in Code B.
I think both mViewMode_A and mViewMode_B will point the same instance in Code B.
But I don't know why I get Result B when I run Code B, could you tell me?
Result B
java.lang.RuntimeException: Cannot create an instance of class info.dodata.soundmeter.presentation.viewmodel.SoundViewModel
Code A
#Composable
fun NavGraph(
mViewModel_A: SoundViewModel = viewModel()
) {
ScreenHome(mViewMode_B = mViewMode1_A)
}
#Composable
fun ScreenHome(
mViewModel_B: SoundViewModel
) {
...
}
#HiltViewModel
class SoundViewModel #Inject constructor(
#ApplicationContext private val appContext: Context,
...
): ViewModel() {
...
}
Code B
#Composable
fun NavGraph(
mViewMode_A: SoundViewModel = viewModel()
) {
ScreenHome()
}
#Composable
fun ScreenHome(
mViewMode_B: SoundViewModel = viewModel() // I think mViewMode_A will be assigned to mViewMode_B automatically without creating a new instnace.
) {
...
}
//The same
You need to pass the key in ViewModel initialization
I'm not good at Composable but this will resolve your problem
For creating a new instance of ViewModel we need to set Key property
ViewModelProvider(requireActivity()).get(<UniqueKey>, SoundViewModel::class.java)
key – The key to use to identify the ViewModel.
val mViewMode_A = viewModel<SoundViewModel>(key = "NavGraph")
val mViewMode_B = viewModel<SoundViewModel>(key = "ScreenHome")
For Composable this link may help you separateViewmodel
I am attempting to add Hilt DI to a view model class.
It's not clear to be from the docs (in layman's terms) what the constructor is used for?
All the Google docs also indicate you need to add this : ViewModel interface after the constructor but I am also curious what purpose that interface serves?
#HiltViewModel
class MyViewModel #Inject constructor(
) : ViewModel() {
private val _myVal: MutableState<String> = mutableStateOf("")
val myVal get() = _myVal.value
fun setMyVal (myVal: String) {
_myVal.value = phoneNumber
}
}
In my activity, I have multiple variables being initiated from Intent Extras. As of now I am using ViewModelFactory to pass these variables as arguments to my viewModel.
How do I eliminate the need for ViewModelFacotory with hilt
Here are two variables in my Activity class
class CommentsActivity : AppCompatActivity() {
private lateinit var viewModel: CommentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
val contentId = intent.getStringExtra(CONTENT_ID_FIELD) //nullable strings
val highlightedCommentId = intent.getStringExtra(HIGHLIGHTED_COMMENT_ID_RF) //nullable strings
val commentsViewModelFactory = CommentsViewModelFactory(
contentId,
highlightedCommentId
)
viewModel = ViewModelProvider(this, commentsViewModelFactory[CommentsViewModel::class.java]
}
}
Here is my viewModel
class CommentsViewMode(
contentId : String?,
highlightedCo;mmentId : String?,
) : ViewModel() {
//logic code here
}
My app is already set up to use hilt but in this case How can I pass these 2 variables and eliminate the viewModelFactory entirely
The trick is to initialize those variables only once, while the activity can be created multiple times. In my apps, I use a flag.
View model:
class CommentsViewModel : ViewModel() {
private var initialized = false
private var contentId : String? = null
private var highlightedCommentId : String? = null
fun initialize(contentId : String?, highlightedCommentId : String?) {
if (!initialized) {
initialized = true
this.contentId = contentId
this.highlightedCommentId = highlightedCommentId
}
}
//logic code here
}
Also, you should know that there is an open issue in dagger project exactly for this capability:
https://github.com/google/dagger/issues/2287
You're welcome to follow the progress.
If you want to use hilt effectively u can follow this steps
Use #HiltViewModel in your view model
#HiltViewModel
class MyViewModel #inject constructor(private val yrParameter): ViewModel {}
Also you no longer need any ViewModelFactory! All is done for you! In your activity or fragment, you can now just use KTX viewModels() directly.
private val viewModel: MyViewModel by viewModels()
Or if you want to use base classes for fragment and activity you can use this code to pass viewModel class
abstract class BaseFragment<V: ViewModel, T: ViewDataBinding>(#LayoutRes val layout: Int, viewModelClass: Class<V>) : Fragment() {
private val mViewModel by lazy {
ViewModelProvider(this).get(viewModelClass)
}
}
I am currently building an app and I have added Dagger Hilt in order to define a single class to access data. the injection seems working fine but I am not able to store a value in the data class I use.
I have created a Singleton first, which is used by the code to set/get value from a data structure.
#Singleton
class CarListMemorySource #Inject constructor() : CarListInterface {
private var extendedCarList: ExtendedCarList? = null
override fun setListOfVehicles(listOfVehicles: List<item>)
{
extendedCarList?.listOfVehicles = listOfVehicles
}
}
When I am calling setListOfVehicles the listOfVehicules contains 10 items but
The data structure ExtendedCarList is defined as below:
data class ExtendedCarList(
var listOfVehicles: List<item>
)
The Singleton in passed using Hilt like for example in the viewModel below:
#HiltViewModel
class HomeScreenViewModel #Inject constructor(
private val carList: CarListMemorySource
): ViewModel() {
fun getList() {
--> DO SOMETHING TO Get A ListA
carList.setListOfVehicles(ListA)
}
}
And in the activity, using the viewModel, I am just doing this:
#AndroidEntryPoint
class HomeScreenActivity: AppCompatActivity() {
private val viewModel: HomeScreenViewModel by viewModels()
....
viewModel.getList()
....
}
Any idea why and how to fix it ?
Thanks
you never initialize extendedCarList.
extendedCarList?.listOfVehicles = listOfVehicles
above line is exactly the same as
if (extendedCarList != null) extendedCarList.listOfVehicles = listOfVehicles
But it never passes the null check.
I think just changing
private var extendedCarList: ExtendedCarList? = null
to
private val extendedCarList = ExtendedCarList()
might solve it
I am new to Android development. Currently, I am using Jetpack Compose to build Android apps. I am also learning with MVVM architecture.
One thing I don't understand with this architecture is why we need to use ViewModelProvider.Factory to pass view model to a screen.
For example,
Instead of this,
#Composable
fun HomeScreen() {
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val repository = InMemoryPlantService()
#Suppress("UNCHECKED_CAST")
return HomeViewModel(
plantRepository = repository
) as T
}
}
val homeViewModel: HomeViewModel = viewModel(null, factory)
val currentState: State<HomeViewState> = homeViewModel.viewState.collectAsState()
HomeScreenScaffold(currentState.value)
}
Can't we do this,
#Composable
fun HomeScreen() {
val repository = InMemoryPlantService()
val homeViewModel: HomeViewModel = HomeViewModel(
plantRepository = repository
)
val currentState: State<HomeViewState> = homeViewModel.viewState.collectAsState()
HomeScreenScaffold(currentState.value)
}
Please help.
Full source code can be found here: https://github.com/adammc331/bloom
HomeScreen can be found here: https://github.com/AdamMc331/Bloom/blob/development/app/src/main/java/com/adammcneilly/bloom/HomeScreen.kt
When you call:
val homeViewModel: HomeViewModel = viewModel(null, factory)
The function viewModel(...) will create a new HomeViewModel if it's the first time you request the ViewModel, or it will return the previous instance of HomeViewModel if it already exists. That's one of the advantages of using ViewModels, because on configuration change (or on recomposition) your ViewModel should be reused, not created again. And the way it works is by using a ViewModelProvider.Factory to create the ViewModel when it's necessary. Your ViewModel has a parameter on its constructor, there's no way the default Android classes would know how to create your ViewModel and pass that parameter (i.e. the repository) without you providing a custom ViewModelProvider.Factory. If your ViewModel doesn't have any parameters, the default ViewModelProvider.Factory uses reflection to create your class by using the no-argument constructor.
If you do this:
val homeViewModel: HomeViewModel = HomeViewModel(
plantRepository = repository
)
Your ViewModel will be created many times and won't be reused across configuration changes or recompositions because you're always creating it there - instead of asking for it to be created or reusing it if it already exists, which is what the viewModel(...) function does.
As per a codelab in Room,
By using viewModels and ViewModelProvider.Factory,the framework will take care of the lifecycle of the ViewModel. It will survive configuration changes and even if the Activity is recreated, you'll always get the right instance of the WordViewModel class.
You do not have to use ViewModelProvider.Factory to instantiate your ViewModel.
Lets assume you have an Entity:
#Entity(tableName = "user")
data class User(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "user_id") val userId: Long)
And a DAO for that entity:
#Dao
interface UserDao {//some methods}
Without using a repository you can instantiate your ViewModel with the help of android.app.Application like so:
class UserViewModel(
application: Application
) : AndroidViewModel(application) {
val dao = AppDatabase.getDatabase(application, viewModelScope).userDao()
}
And then later in a Fragment create your ViewModel which you can later pass into your composable:
private val userViewModel: userViewModel by viewModels()