in User repository, I am fetching the data from the database and I cast into the model class User.
How do I access two variables user and email of the model class inside the live data?
ViewModel
class MyViewModel (private val repository: UsrRepository) : ViewModel() {
private var cUser : LiveData<User> = repository.getCacheUser()
val user: LiveData<String> = cUser.user
val email: LiveData<String> = cUser.email
}
Xml
<TextView
android:text="#{viewmodel.user}"
<TextView
android:text="#{viewmodel.email}"
Since you are not doing any transformations on the data provided by cUser, you can just make it public in your ViewModel:
val cUser: LiveData<User> = repository.getCacheUser()
Then access the user properties from the LiveData directly in the layout:
android:text="#{viewmodel.cUser.user}"
android:text="#{viewmodel.cUser.email}"
If you did not want to use the raw values, and instead some other computed values, you would instead expose a transformation of cUser, creating a new LiveData that has values modified from the original.
Related
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.
When my application starts I create object with some data and I want to share the same instance of object between services/viewModels.
Is it possible to inject the same instance of data class to viewModel using Koin?
Edit:
I create user object in MainViewModel when app loaded data from firebase at start.
#IgnoreExtraProperties
#Keep
data class User(
val id: String = "",
val name: String? = null,
val surname: String? = null,
val email: String? = null,
val avatarUrl: String? = null
)
If your view model inherits from KoinComponent, you can access getKoin method to declare your user object.
class MainViewModel : ViewModel(), KoinComponent {
The user object will be available to rest of your application after declaration.
// user created from data from firebase ...
fun insertKoinFor(user: User) {
// declare koin the user of type User
getKoin().declare<User>(user)
// or declare with a named qualifier
getKoin().declare(user, named("myUser"))
}
Hope, it helps.
I would create a holder object, say UserManager, to hold an optional User instance. This holder is something you can provide in your koin graph as single, and whatever component responsible setting up the User instance (for example your MainViewModel) can update the instance inside the singleton holder.
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()
I've met an issue with observing data in my app.
For the testing purposes, I have an activity with a single text view where I show the user's name. This is the code:
#Entity(tableName = "User")
data class User(
var name: String,
var surname: String,
#PrimaryKey(autoGenerate = true)
val internalID: Long = 0)
in the dao I've got just one method:
#Query("SELECT * FROM User WHERE surname LIKE :surname")
abstract suspend fun getUserForSurname(surname: String): User
in the activity onCreate's method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val model = ViewModelProvider(this).get(MainViewModel::class.java)
binding.viewmodel = model
model.user.observe(this, Observer {
binding.textTest.setText(it.name)
})
}
and finally, view model:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val surname = "Doe"
val user: MutableLiveData<User> = MutableLiveData()
private val userDao: UserDao =
MyRoomDatabase.getDatabase(application).clientDao()
init {
viewModelScope.launch {
user.value = userDao.getUserForSurname(surname)
}
}
}
That specific user's name is changed in the background thread. When I check for the value in db itself, the name is indeed different. After restarting activity, the text view is changed too. In other words: the db value is changed but the observer is never called. I know that I am asking for the value only once during viewmodel's init method and it may be a problem. Is it possible to see the actual change without restarting activity?
I suggest you have a look at this Codelab
Room exposes different wrappers around the returned Entities such as:
RxJava
Flow Coroutines
LiveData
So you can changed your Dao as such:
#Query("SELECT * FROM User WHERE surname LIKE :surname")
abstract fun getUserForSurname(surname: String): LiveData<User>
The above means that any changes to the user entry will emit an observation to the listeners of the LiveData.
ViewModel
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val surname = "Doe"
lateinit val client: LiveData<User>
private val userDao: UserDao =
MyRoomDatabase.getDatabase(application).clientDao()
init {
viewModelScope.launch {
user = userDao.getUserForSurname(surname)
}
}
}
Read more at:
- https://developer.android.com/training/data-storage/room/index.html
Disclaimer: Didn't test the above solution but it should give you an idea.
EDIT: Ideally LiveData should only be used in your view model as they were designed for such cases and not to observe DB transactions. I will suggest to replace the Dao's with Coroutine's Flow and use the extension to convert to LiveData.
I'm creating the app uses weather API and I need to get name of the place from Edit Text in UI to the ViewModel and there is val which gets method from Repository. How to correctly communicate with ViewModel in my case? There is some magic spell in LiveData or I need Databinding?
ViewModel:
class MainViewModel(
private val weatherRepository: WeatherRepository
) : ViewModel() {
val metric: String = "metric"
val currentWeatherByCoordinates by lazyDeferred {
weatherRepository.getCurrentWeather() }
val forecastByCoordinates by lazyDeferred {
weatherRepository.getForecast() }
val currentWeatherByCity by lazyDeferred { //This is what I'm writing about
weatherRepository.getCurrentWeatherByCity("London", metric)
}
}
use live data. observe some live data method1() in ui -> change the livedata state in viewmodel by method2(city)