Android: Variable gets uninitialized in ViewModel after being initialized in the Fragment - android

I have a callback method in my fragment which gets called from it's ViewModel. It initializes the variable in the OnCreateView() method of the fragment, but when the ViewModel calls it to use it, its null.
I am thinking that it has something to do with maybe the VM getting recreated somehow? I just can't seem to figure it out.
I am following this answer's of how the VM drives the UI. They provide Google's sample of a callback interface being created (TasksNavigator.java), Overriding the method in the View (TasksActivity.java), and then calling that method from the VM (TasksViewModel.java) but it doesn't seem to work for me.
Fragment
class SearchMovieFragment : Fragment(), SearchNavigator {
companion object {
fun newInstance() = SearchMovieFragment()
}
private lateinit var searchMovieFragmentViewModel: SearchMovieFragmentViewModel
private lateinit var binding: SearchMovieFragmentBinding
private lateinit var movieRecyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
binding.viewmodel = searchMovieFragmentViewModel
searchMovieFragmentViewModel.setNavigator(this)
setUpRecyclerView(container!!.context)
return binding.root
}
private fun setUpRecyclerView(context: Context) {
movieRecyclerView = binding.searchMovieFragmentRecyclerView.apply {
this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
val adapter = MovieListAdapter()
binding.searchMovieFragmentRecyclerView.adapter = adapter
searchMovieFragmentViewModel.movieList.observe(viewLifecycleOwner, Observer {
adapter.submitList(it)
})
}
override fun openDetails() {
Log.d("TEST", "opening details")
}
}
ViewModel
class SearchMovieFragmentViewModel : ViewModel(), MovieSearchItemViewModel {
private lateinit var searchNavigator: SearchNavigator
val editTextContent = MutableLiveData<String>()
var movieList = Repository.getMovieList("batman")
fun setNavigator(_searchNavigator: SearchNavigator) {
this.searchNavigator = _searchNavigator
if (searchNavigator != null) {
Log.d("TEST", "its not null $searchNavigator") // Here it is not null
}
}
private fun getMovieDetail(movieId: String) {
val movie = Repository.getMovieDetail(movieId)
Log.d("TEST", "checking ${this.searchNavigator}") // Here is where I call it but it is null
// searchNavigator.openDetails()
}
private fun getMovieList(movieSearch: String): MutableLiveData<List<Movie>> = Repository.getMovieList(movieSearch)
override fun displayMovieDetailsButton(movieId: String) {
Log.d("TEST", "button clicked $movieId")
getMovieDetail(movieId)
}
}
CallBack Interface
interface SearchNavigator {
fun openDetails()
}

Initiate ViewModel in below method of fragment
override onActivityCreated(#Nullable final Bundle savedInstanceState){
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
}
I will recommend use live data to create connection between ViewModel and and Fragment it will be safer and correct approach.
Trigger openDetails based on the trigger's from your live data.It's forbidden to send your view(context) instance to ViewModel even if you wrap it as there is high probability of memory leaks.
But if you still want to follow this approach then you should Register and unregister fragment instance in your ViewModel (keep a list of SearchNavigator) it onStop() and onStart() .
and loop through them to call openDetails

Related

LiveData binding observer being registered multiple times when changing device orientation even when using viewLifeCycleOwner

I have a view model that is data binded to a fragment. The view model is shared with the main activity.
I've button is binded to the view as follows:
<Button
android:id="#+id/startStopBtn"
android:text="#{dashboardViewModel.startStopText == null ? #string/startBtn : dashboardViewModel.startStopText}"
android:onClick = "#{() -> dashboardViewModel.onStartStopButton(context)}"
android:layout_width="83dp"
android:layout_height="84dp"
android:layout_gravity="center_horizontal|center_vertical"
android:backgroundTint="#{dashboardViewModel.isRecStarted == false ? #color/startYellow : #color/stopRed}"
tools:backgroundTint="#color/startYellow"
android:duplicateParentState="false"
tools:text="START"
android:textColor="#FFFFFF" />
What I expect to happen is that every time I press the button the function onStartStopButton(context) runs. This works fine as long as I don't rotate the device. When I rotate the device the function is run twice, if I rotate again the function is run 3 times and so on. This is not a problem if I go to another fragment and then back to the dashboard fragment. It looks like the live data observer is getting registered every time I rotate my screen, but not every time I detach and reattach the fragment.
This is true for all the elements in that fragment, whether they are data binded or I manually observe them.
Fragment code:
class DashboardFragment : Fragment() {
private var _binding: FragmentDashboardBinding? = null
private val binding get() = _binding!!
private val dashboardViewModel: DashboardViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
val root: View = binding.root
binding.dashboardViewModel = dashboardViewModel
binding.lifecycleOwner = viewLifecycleOwner
dashboardViewModel.bleSwitchState.observe(viewLifecycleOwner, Observer { switchState -> handleBleSwitch(switchState) })
dashboardViewModel.yLims.observe(viewLifecycleOwner, Observer { yLims ->
updatePlotWithNewData(yLims.first, yLims.second)
})
Timber.i("Dahsboard on create: DashboardViewModel in fragment: $dashboardViewModel")
return root
}
}
The view model:
class DashboardViewModel : ViewModel() {
//region live data
private var _isRecStarted = MutableLiveData<Boolean>()
val isRecStarted: LiveData<Boolean> get() = _isRecStarted
//private var _bleSwitchState = MutableLiveData<Boolean>()
val bleSwitchState = MutableLiveData<Boolean>()
private var _startStopText = MutableLiveData<String>()
val startStopText: LiveData<String> get() = _startStopText
private var _yLims = MutableLiveData<Pair<kotlin.Float,kotlin.Float>>()
val yLims: LiveData<Pair<kotlin.Float,kotlin.Float>> get() = _yLims
//endregion
init {
Timber.d("DashboardViewModel created!")
bleSwitchState.value = true
}
//region start stop button
fun onStartStopButton(context: Context){
Timber.i("Start stop button pressed, recording data size: ${recordingRawData.size}, is started: ${isRecStarted.value}")
isRecStarted.value?.let{ isRecStarted ->
if (!isRecStarted){ // starting recording
_isRecStarted.postValue(true)
_startStopText.postValue(context.getString(R.string.stopBtn))
startDurationTimer()
}else{ // stopping recording
_isRecStarted.postValue(false)
_startStopText.postValue(context.getString(R.string.startBtn))
stopDurationTimer()
}
} ?: run{
Timber.e("Error! Is rec started is not there for some reason")
}
}
}
The view model is created the first time from the MainActivity as follows:
class MainActivity : AppCompatActivity() {
private val dashboardViewModel: DashboardViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("DashboardViewModel in main activity: $dashboardViewModel")
}
}
Edit explaining why the MainActivity is tided to the ViewModel:
The reason why the ViewModel is linked to the main activity is that the main activity handles some Bluetooth stuff for a stream of data, when a new sample arrives then the logic to handle it and update the UI of the dashboard fragment is on the DashboardViewModel. The data still needs to be handled even if the dashboard fragment is not there.
So I need to pass the new sample to the DashboardViewModel from the main activity as that is where I receive it. Any suggestions to make this work?
As you know, when you instantiate the ViewModel of a Fragment with activityViewModels, it means that the ViewModel will follow the lifecycle of the Activity containing that Fragment. Specifically here is MainActivity.
So what does ViewModel tied to Activity lifecycle mean in your case?
When you return to the Fragment, normally LiveData (with ViewModel attached to Fragment lifcycler) will trigger again.
But when that ViewModel is attached to the Activity's lifecycle, the LiveData will not be triggered when returning to the Fragment.
That leads to when you return to the Fragment, your LiveData doesn't trigger again.
And that LiveData only triggers according to the life cycle of the activity. That is, when you rotate the screen, the Activity re-initializes, now your LiveData is triggered.
EDIT:
Here, I will give you one way. Maybe my code below doesn't work completely for your case, but I think it will help you in how to control LiveData and ViewModel when you bind ViewModel to Activity.
First, I recommend that each Fragment should have its own ViewModel and it should not depend on any other Fragment or Activity. Here you should rename the DashboardViewModel initialized by activityViewModels() as ShareViewModel or whatever you feel it is related to this being the ShareViewModel between your Activity and Fragment.
class DashboardFragment : Fragment() {
// Change this `DashboardViewModel` to another class name. Could be `ShareViewModel`.
private val shareViewModel: ShareViewModel by activityViewModels()
// This is the ViewModel attached to the DashboardFragment lifecycle.
private val viewModel: DashboardViewModel by viewModels()
private lateinit var _binding: FragmentDashboardBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.dashboardViewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}
Next, when there is data triggered by the ShareViewModel's LiveData, you will set the value for the LiveData in the ViewModel associated with your Fragment. As follows:
DashboardViewModel.kt
class DashboardViewModel: ViewModel() {
private val _blueToothSwitchState = MutableLiveData<YourType>()
val blueToothSwitchState: LiveData<YourType> = _blueToothSwitchState
private val _yLims = MutableLiveData<Pair<YourType, YourType>>()
val yLims: LiveData<Pair<YourType, YourType>> = _blueToothSwitchState
fun setBlueToothSwitchState(data: YourType) {
_blueToothSwitchState.value = data
}
fun setYLims(data: Pair<YourType, YourType>) {
_yLims.value = data
}
}
DashboardFragment.kt
class DashboardFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
shareViewModel.run {
bleSwitchState.observe(viewLifeCycleOwner) {
viewModel.setBlueToothSwitchState(it)
}
yLims.observe(viewLifeCycleOwner) {
viewModel.setYLims(it)
}
}
viewModel.run {
// Here, LiveData fires observe according to the life cycle of `DashboardFragment`.
// So when you go back to `DashboardFragment`, the LiveData is re-triggered and you still get the observation of that LiveData.
blueToothSwitchState.observe(viewLifeCycleOwner, ::handleBleSwitch)
yLims.observe(viewLifeCycleOwner) {
updatePlotWithNewData(it.first, it.second)
}
}
}
...
}
Edit 2:
In case you rotate the device, the Activity and Fragment will be re-initialized. At that time, LiveData will fire observe. To prevent that, use Event. It will keep your LiveData from observing the value until you set the value again for LiveData.
First, let's create a class Event.
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set
fun getContentIfNotHandled(): T? = if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
fun peekContent(): T = content
}
Next, modify the return type of the LiveData that you want to trigger once.
ShareViewModel.kt
class ShareViewModel: ViewModel() {
private val _test = MutableLiveData<Event<YourType>>()
val test: LiveData<Event<YourType>> = _test
fun setTest(value: YourType) {
_test.value = Event(value)
}
}
Add this extension to easily get LiveData's observations.
LiveDataExt.kt
fun <T> LiveData<Event<T>>.eventObserve(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner) { it?.getContentIfNotHandled()?.let(observer) }
}
Finally in the view, you get the data observed by LiveDatat.
class DashboardFragment : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
shareViewModel.test.eventObserve(viewLifeCycleOwner) {
Timber.d("This is test")
}
}
...
}
Note: When using LiveData with Event, make sure that LiveData is not reset when rotating the device. If LiveData is set to value again, LiveData will still trigger even if you use Event.

Why return function in variable is null? Kotlin + Android

I am missing some basic coding knowledge here I think, I want to present value to the fragment by assigning the function to the variable in a viewModel. When I call the function directly, I get correct value. When I assign function to variable and pass the variable to the fragment it is always null, why?
View Model
class CartFragmentViewModel : ViewModel() {
private val repository = FirebaseCloud()
private val user = repository.getUserData()
val userCart = user?.switchMap {
repository.getProductsFromCart(it.cart)
}
private fun calculateCartValue(): Long? {
val list = userCart?.value
return list?.map { it.price!! }?.sum()
}
//val cartValue = userCart?.value?.sumOf { it.price!! } <- THIS will be null
val cartValue = calculateCartValue() <- THIS will be null
val cartSize = userCart?.value?.size <- THIS will be null
}
Fragment
class CartFragment : RootFragment(), OnProductClick, View.OnClickListener {
private lateinit var cartViewModel: CartFragmentViewModel
private lateinit var binding: FragmentCartBinding
private val cartAdapter = CartAdapter(this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_cart,
container,
false
)
setAnimation()
cartViewModel = CartFragmentViewModel()
binding.buttonToCheckout.setOnClickListener(this)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerCart.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = cartAdapter
}
cartViewModel.userCart?.observe(viewLifecycleOwner, { list ->
cartAdapter.setCartProducts(list)
updateCart()
})
}
override fun onClick(view: View?) {
when (view) {
binding.buttonToCheckout -> {
navigateToCheckout(cartViewModel.cartValue.toString())
cartViewModel.sendProductEvent(
cartAdapter.cartList,
ProductEventType.CHECKOUT
)
}
}
}
override fun onProductClick(product: Product, position: Int) {
cartViewModel.removeFromCart(product)
cartAdapter.removeFromCart(product, position)
updateCart()
}
private fun updateCart() {
binding.textCartTotalValue.text = cartViewModel.cartValue.toString() <- NULL
binding.textCartQuantityValue.text = cartViewModel.cartSize.toString() <- NULL
}
}
Thanks!
It looks like userCart is some sort of observable variable which initially holds a null value and then gets populated with the data from your repository after the network call (or something similar) completes.
The reason that all your variables are null are because you are declaring their value immediately, so by the time those statements get executed, the network call hasn't yet completed and userCart?.value is null. However calling the calculateCartValue() function later on in the code might yield a value if the fetch is complete.

Remove Current Fragment and Launch Another Fragment from View Model

below is my ViewModel class which accepts application:Application as parameter.I want to launch another fragment from this class.But in remove() method,how do I pass fragment.
class EmailConfirmationFragmentViewModel(application: Application) : AndroidViewModel(application) {
private lateinit var viewModelApplication: Application
init {
this.viewModelApplication = application
}
var email = MutableLiveData<String>()
private var emailMutableLiveData: MutableLiveData<UserEmail>? = null
val userEmail: MutableLiveData<UserEmail>
get() {
if (emailMutableLiveData == null) {
emailMutableLiveData = MutableLiveData<UserEmail>()
}
return emailMutableLiveData!!
}
fun onEmailChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.toString() != null && !s.toString().equals(""))
email.value = s.toString()
}
fun onConfirmClicked(view: View) {
userEmail.value = UserEmail(email.value.toString())
launchResetPasswordFragment()
}
private fun launchResetPasswordFragment() {
try {
(viewModelApplication as FragmentActivity).supportFragmentManager.beginTransaction()
.replace(R.id.fl_Wrapper, OtpVerificationFragement()).remove(viewModelApplication.applicationContext).commit()
}
catch(e:Exception)
{
Log.e("Error","$e")
}
}
}
Lifecycle events and Fragment transactions should never take place inside of a view model. As discussed in the ViewModel Overview, a "ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context." While the AndroidViewModel does introduce an anti-pattern by exposing a reference to the application, this specific use case is not an appropriate one. In situations where the view model should invoke a fragment transaction, it's most commonly handled by the general concept of an event dispatched from the view model to the Lifecycle Owner. I believe employing such a pattern can resolve your issue. While I don't know the state of your Fragment, I've devised a likely solution.
class EmailConfirmationViewModel() : ViewModel() {
val email: MutableLiveData<String> = MutableLiveData()
private val _resetFragment: MutableLiveData<Event> = MutableLiveData()
val resetFragment: LiveData<Event> = _resetFragment
val userEmail: UserEmail?
get() = email.value?.let { UserEmail(it) }
fun onEmailChanged(s: CharSequence) {
email.value = s.toString()
}
fun onConfirmClicked() {
resetFragment()
}
private fun resetFragment() {
_resetFragment.value = Event()
}
}
Where the supporting event classes could appear as such:
class Event : EventWithValue<Unit>(Unit)
open class EventWithValue<T>(
private val value: T,
) {
private var isHandled = false
fun getValueIfUnhandled(): T? = if (isHandled) {
null
} else {
handleValue()
}
private fun handleValue(): T {
isHandled = true
return value
}
}
class EventObserver<T>(
private val eventIfUnhandled: (value: T) -> Unit,
) : Observer<EventWithValue<T>?> {
override fun onChanged(event: EventWithValue<T>?) {
event?.getValueIfUnhandled()?.let { eventIfUnhandled(it) }
}
}
Through observing the event in the Fragment itself, you eliminate the need to reference any sort of view in the view model while maintaining the view model's role as the dispatcher. Here's a brief description of how you would listen to the event from your Lifecycle Owner, in this case, a Fragment.
class EmailConfirmationFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View? = super.onCreateView(inflater, container, savedInstanceState)
val viewModel: EmailConfirmationViewModel by viewModels()
viewModel.resetFragment.observe(viewLifecycleOwner, EventObsever {
// Call a function of the activity's viewModel (ideal), or complete the transaction here through referencing the activity directly (ill-advised)
})
return view
}
}
I think exposing userEmail is a bit of a code smell in itself. Alternatively, you could define the resetFragment event as
private val _resetFragment: MutableLiveData<EventWithValue<UserEmail>> = MutableLiveData()
val resetFragment: LiveData<EventWithValue<UserEmail>> = _resetFragment
and receive the value of the userEmail directly within the event listener featured above. This would remove the need to expose the userEmail of the view model.

Android: Livedata Observer gets never called, recylerview list is never submitted, navgraphviewmodel

I have a ShopFilterFragmentProductFilter which is inside a ShopFilterFragmentHolder which itself holds a ViewPager2. This ShopFilterFragmentHolder is a DialogFragment which is opened inside my ShopFragment. So ShopFragment -> ShopFilterFragmentHolder (Dialog, ViewPager2) -> ShopFilterFragmentProductFilter. ALL of these Fragments should share the same navgraphscoped viewmodel.
The problem I have is, that when I attach an observer inside my ShopFilterFragmentProductFilter to get my recyclerview list from cloud-firestore, this observer never gets called and therefore I get the error message "No Adapter attached, skipping layout". I know that this is not a problem with how I instantiate and assign the adapter to my recyclerview, because when I set a static list (e.g creating a list inside my ShopFilterFragmentProductFilter) everything works.
Why do I don't get the livedata value? To my mind, there is a problem with the viewmodel creation.
Here is my current approach:
ShopFilterFragmentProductFilter
#AndroidEntryPoint
class ShopFilterFragmentProductFilter : Fragment() {
private var _binding: FragmentShopFilterItemBinding? = null
private val binding: FragmentShopFilterItemBinding get() = _binding!!
private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
#Inject lateinit var shopFilterItemAdapter: ShopFilterItemAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShopFilterItemBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
submitAdapterList()
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvShopFilter.adapter = null
_binding = null
}
private fun bindObjects() {
with(binding) {
adapter = shopFilterItemAdapter
}
}
private fun submitAdapterList() {
shopViewModel.shopProductFilterList.observe(viewLifecycleOwner) {
shopFilterItemAdapter.submitList(it)
shopFilterItemAdapter.notifyDataSetChanged()
toast("SUBMITTED LIST") // this does never get called
}
/* // this works
shopFilterItemAdapter.submitList(
listOf(
ShopFilterItem(0, "ITEM 1"),
ShopFilterItem(0, "ITEM 2"),
ShopFilterItem(0, "ITEM 3"),
ShopFilterItem(0, "ITEM 4"),
ShopFilterItem(0, "ITEM 5"),
)
)
*/
}
}
ViewModel
class ShopViewModel #ViewModelInject constructor(
private val shopRepository: ShopRepository,
private val shopFilterRepository: ShopFilterRepository
) : ViewModel() {
private val query = MutableLiveData(QueryHolder("", ""))
val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }
val shopProductFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getProductFilterList() }
val shopListFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getListFilterList() }
fun search(newQuery: QueryHolder) {
this.query.value = newQuery
}
}
ShopFilterRepositoryImpl
class ShopFilterRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : ShopFilterRepository {
override suspend fun getProductFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.productFilter
override suspend fun getListFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.listFilter
}
Nav_graph
Probably, you should define it as MutableLiveData:
private val shopProductFilterList: MutableLiveData<List<ShopFilterItem>> = MutableLiveData()
And in a method in your viewModel that gets the data through repository, you should post the LiveData value:
fun getProductFilterList() = viewModelScope.launch {
val dataFetched = repository. getProductFilterList()
shopProductFilterList.postValue(dataFetched)
}

Fun with flows, getting null when converting to live data

I am trying out flows and trying to see how they can be converted to mvvm with android view models. Here is what I tried first to test it out :
class HomeViewModel : ViewModel() {
private lateinit var glucoseFlow: LiveData<Int>
var _glucoseFlow = MutableLiveData<Int>()
fun getGlucoseFlow() {
glucoseFlow = flowOf(1,2).asLiveData()
_glucoseFlow.value = glucoseFlow.value
}
}
class HomeFragment : Fragment() {
private lateinit var viewModel: HomeViewModel
override fun onCreateView (
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.home_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
viewModel._glucoseFlow.observe(this, Observer {
handleUpdate(it)
})
viewModel.getGlucoseFlow()
}
private fun handleUpdate(reading : Int) {
glucose_reading.text = reading.toString()
}
}
I get a null for the reading number however any ideas ?
This happens because you are trying to assign glucoseFlow.value to _glucoseFlow.value directly, I guess you should use a MediatorLiveData<Int>, however this is not my final suggestion.
You can solve it if you collect flow items and then assign them to your private variable.
// For private variables, prefer use underscore prefix, as well MutableLiveData for assignable values.
private val _glucoseFlow = MutableLiveData<Int>()
// For public variables, prefer use LiveData just to read values.
val glucoseFlow: LiveData<Int> get() = _glucoseFlow
fun getGlucoseFlow() {
viewModelScope.launch {
flowOf(1, 2)
.collect {
_glucoseFlow.value = it
}
}
}
Having the before implementation over the HomeViewModel, start to observe your public glucoseFlow from HomeFragment and you will be able to receive non-null sequence values (1 and then 2).
If you are using databinding, do not forget specify the fragment view as the lifecycle owner of the binding so that the binding can observe LiveData updates.
class HomeFragment : Fragment() {
...
binding.lifecycleOwner = viewLifecycleOwner
}

Categories

Resources