Android Data Binding + Mediator Live Data - Handling Lifecycle Events - android-fragments

In my Android app I have a fragment where the user can simultaneously view and edit some object's attributes.
I'm using an MVVM architecture with data binding and a mediator live data that holds the Relation object being edited. Here's how it works:
Fragment inflates and binds the view (the layout xml).
During this process the Fragment has a ViewModel made.
The ViewModel will fetch the Relation object (and its attributes) from the database, and put it in a MediatorLiveData.
Thanks to data binding and binding adapters, the editText fields get set to the object's attributes automatically.
The user can then edit these editText fields and save.
Upon saving, the ViewModel will get the texts from the editTexts and use them to update the Relation object in the local database
Here's the problem: upon rotating the screen, the fragment gets destroyed and recreated. But I have no way to restore the editText contents. The binding will just reset the editText contents (because we didn't actually update the Relation object attributes yet, we only do that when the user presses 'save').
I can't use a Bundle / savedInstanceState because the binding will just overwrite that. Using a MediatorLiveData to hold the edited contents won't work either because the ViewModel gets destroyed upon rotation, so we lose that data.
Portion of the fragment layout. Note the data variable (viewmodel) and the data binding in the relationNameEditText:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".presentation.relationdetail.RelationDetailFragment">
<data>
<variable
name="relationDetailViewModel"
type="be.pjvandamme.farfiled.presentation.relationdetail.RelationDetailViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="#dimen/relationDetailLayoutMargin">
<TextView
android:id="#+id/nameLabelTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginBottom="8dp"
android:text="#string/nameLabel"
app:layout_constraintBottom_toTopOf="#+id/relationNameEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/relationNameEditText"
android:layout_width="#dimen/relationNameEditWidth"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:ems="10"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="#+id/editTextChips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/nameLabelTextView"
app:relationName="#{relationDetailViewModel.relation}" />
The Fragment itself:
class RelationDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentRelationDetailBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_relation_detail,
container,
false
)
val application = requireNotNull(this.activity).application
val arguments = RelationDetailFragmentArgs.fromBundle(arguments!!)
val relationDataSource = FarFiledDatabase.getInstance(application).relationDao
val relationLifeAreaDataSource = FarFiledDatabase.getInstance(application).relationLifeAreaDao
val viewModelFactory =
RelationDetailViewModelFactory(
arguments.relationId,
relationDataSource,
relationLifeAreaDataSource,
application
)
val relationDetailViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(RelationDetailViewModel::class.java)
binding.relationDetailViewModel = relationDetailViewModel
binding.setLifecycleOwner(this)
// stuff about chips
val textWatcher = object: TextWatcher{ /* */ }
binding.saveButton.isEnabled = false
binding.relationNameEditText.addTextChangedListener(textWatcher)
binding.relationSynopsisEditText.addTextChangedListener(textWatcher)
binding.lifeAreaNowEditText.addTextChangedListener(textWatcher)
// etc.
relationDetailViewModel.enableSaveButton.observe(this, Observer{ /* */})
relationDetailViewModel.showNameEmptySnackbar.observe(this, Observer{ /* */})
relationDetailViewModel.navigateToRelationsList.observe(this, Observer{ /* */})
return binding.root
}
}
The viewmodel:
class RelationDetailViewModel (
private val relationKey: Long?,
val relationDatabase: RelationDao,
val relationLifeAreaDatabase: RelationLifeAreaDao,
application: Application
): AndroidViewModel(application) {
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val relation = MediatorLiveData<Relation?>()
fun getRelation() = relation
private val relationLifeAreas = MediatorLiveData<List<RelationLifeArea?>>()
fun getRelationLifeAreas() = relationLifeAreas
// other LiveData's with backing properties, to trigger UI events
init {
initializeRelation()
}
private fun initializeRelation(){
if(relationKey == null || relationKey == -1L) {
initializeNewRelation()
getAdorableAvatarFacialFeatures()
}
else {
retrieveAvatarUrl()
relation.addSource(
relationDatabase.getRelationWithId(relationKey),
relation::setValue)
relationLifeAreas.addSource(
relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(relationKey),
relationLifeAreas::setValue)
}
}
private fun initializeNewRelation(){
uiScope.launch{
var relationId = insert(Relation(0L,"","",null,false))
initializeLifeAreasForRelation(relationId)
relation.addSource(
relationDatabase.getRelationWithId(
relationId!!),
relation::setValue)
relationLifeAreas.addSource(
relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(
relationId!!),
relationLifeAreas::setValue)
}
}
private fun initializeLifeAreasForRelation(relationId: Long?){
if(relationId != null){
enumValues<LifeArea>().forEach {
uiScope.launch{
var relationLifeArea = RelationLifeArea(0L,relationId,it,"")
insert(relationLifeArea)
}
}
}
}
private fun retrieveAvatarUrl(){
uiScope.launch{
var rel = get(relationKey!!)
var avatarUrl = rel?.avatarUrl
if (avatarUrl.isNullOrEmpty()){
getAdorableAvatarFacialFeatures()
_enableSaveButton.value = true
}
else
_adorableAvatarString.value = rel?.avatarUrl
}
}
private fun getAdorableAvatarFacialFeatures(){
uiScope.launch{
var getFeaturesDeferred = AdorableAvatarApi.retrofitService.getFacialFeatures()
try{
var result = getFeaturesDeferred.await()
_adorableAvatarString.value = "https://api.adorable.io/avatars/face/" +
result.features.eyes.shuffled().take(1)[0] + "/" +
result.features.nose.shuffled().take(1)[0] + "/" +
result.features.mouth.shuffled().take(1)[0] + "/" +
result.features.COLOR_PALETTE.shuffled().take(1)[0]
relation.value?.avatarUrl = _adorableAvatarString.value
} catch(t:Throwable){
// ToDo: what if this fails?? -> Try again later!!
_adorableAvatarString.value = "Failure: " + t.message
}
}
}
fun onEditRelation(
relationNameText: String,
relationSynopsisText: String,
lifeAreaNowText: String,
// etc.
){
_enableSaveButton.value = !compareRelationAttributes(
relationNameText,
relationSynopsisText,
lifeAreaNow.Text,
// etc
)
}
private fun compareRelationAttributes(
relationNameText: String,
relationSynopsisText: String,
lifeAreaNowText: String,
// etc.
): Boolean {
// checks if any of the attributes of the Relation object were changed
// i.e. at least 1 of the editText fields has a text content that does
// does not equal the corresponding attribute of the Relation object
}
fun onSave(
name: String,
synopsis: String,
nowLA: String,
// etc.
){
if(!name.isNullOrEmpty()) {
uiScope.launch {
// update the DB
}
// TODO: this one should go away, need some sort of up button instead
_navigateToRelationsList.value = true
}
else
_showNameEmptySnackbar.value = true
}
// database suspend funs omitted
// ui event handling functions
override fun onCleared(){ /* cancel the viewModelJob */ }
}
The binding adapters:
#BindingAdapter("relationName")
fun TextView.setRelationName(item: Relation?){
item?.let{
text = item.name
}
}
#BindingAdapter("relationSynopsis")
fun TextView.setRelationSynopsis(item: Relation?){
item?.let{
text = item.synopsis
}
}
#BindingAdapter("relationLifeAreaNow")
fun TextView.setLifeAreaNowText(item: List<RelationLifeArea?>?){
item?.let{
item.forEach{
if(it?.lifeArea == LifeArea.EPHEMERA){
text = it.content
}
}
}
}
<!-- etc. -->
So my question is: how do I handle this?
I'm thinking the only solution would be to 1) hold a Relation object with the EDITED attributes, updated whenever the user edits the editText, 2) store this in the database.
But I don't think this would be architecturally sound. Nor am I sure if it would work.

In this case, you can try Two Way Data Binding

Related

How to upgrade to AndroidX preferences?

I'm trying to upgrade to the new Androidx preference library. All tutorials I find involve creating masses of intertwined XML files, activities, and fragments, which makes everything much more complicated that it was before.
What I had before:
class PreferenceItem<T>(val key: String, val default: T){
fun read(context: Context): T = PreferenceManager.getDefaultSharedPreferences(context).all.get(key) as T
}
enum class CoordRep { LATLNG, UTM }
class MyPreferences(context: Context){
val coordSys = PreferenceItem("COORD_SYS", CoordRep.LATLNG)
val showSmoothVideo = PreferenceItem("SHOW_SMOOTH_VIDEO", true)
}
class SettingsActivity : PreferenceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
preferenceScreen = preferenceManager.createPreferenceScreen(this)
val prefs = MyPreferences(this)
preferenceScreen.addPreference(ListPreference(this).apply {
key = prefs.coordSys.key
value = prefs.coordSys.default.name
entries = CoordRep.values().map { when(it){
CoordRep.LATLNG ->"Lat/Long"; CoordRep.UTM ->"UTM"} }.toTypedArray()
entryValues = CoordRep.values().map { it.name }.toTypedArray()
title = "Coordinate Representation"
summary = "Represent Coordinates as: %s"
})
preferenceScreen.addPreference(CheckBoxPreference(this).apply{
key = prefs.showSmoothVideo.key
setDefaultValue(prefs.showSmoothVideo.default)
title = "Smooth Video"
summary = "Enable smooth video"
})
}
Question is - how can I upgrade this code to AndroidX? If I try, I first find that preferenceManager no longer exists because there is no PreferenceActivity for AndroidX.
And trying to do it with
val screen = PreferenceManager(this).createPreferenceScreen(this)
Gives me an error about PreferenceManager can only be called from within the same library group prefix
Is there a straightforward way to switch this over to the newer and supposedly superior AndroidX preference API?
Well, figured it out. Looks like you have to use a fragment.
class PreferenceItem<T>(val key: String, val default: T){
fun read(context: Context): T = PreferenceManager.getDefaultSharedPreferences(context).all.get(key) as T
}
enum class CoordRep { LATLNG, UTM }
class MyPreferences(context: Context){
val coordSys = PreferenceItem("COORD_SYS", CoordRep.LATLNG)
val showSmoothVideo = PreferenceItem("SHOW_SMOOTH_VIDEO", true)
}
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, SettingsFragment())
.commit()
}
}
class SettingsFragment: PreferenceFragmentCompat(){
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val context = preferenceManager.context
preferenceScreen = preferenceManager.createPreferenceScreen(context)
preferenceScreen.addPreference(ListPreference(context).apply {
key = prefs.coordSys.key
value = prefs.coordSys.default.name
entries = CoordRep.values().map { when(it){
CoordRep.LATLNG ->"Lat/Long"; CoordRep.UTM ->"UTM"} }.toTypedArray()
entryValues = CoordRep.values().map { it.name }.toTypedArray()
title = "Coordinate Representation"
summary = "Represent Coordinates as: %s"
})
preferenceScreen.addPreference(CheckBoxPreference(context).apply{
key = prefs.showSmoothVideo.key
setDefaultValue(prefs.showSmoothVideo.default)
title = "Smooth Video"
summary = "Enable smooth video"
})
It assume the existence of this silly XML file (which I would be more than happy to get rid of but don't want to waste any more time on this)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Not sure why this is an "improvement" over the old way but at least you won't have pycharm complaining about deprecation anymore, and you get access to some new widgets like SeekBarPreference.

How can via kotlin mapOf map buttons key to value?

How can via Kotlin mapOf map buttons key to value?
I have such code.
reference to start_button is correct, by clickin on it I can open corect button in xml, but in debug mode buttonsMap value (start_button) is null.
class StartDialog : DialogFragment(){
private val buttonsMap: Map<String, Button> by lazy(LazyThreadSafetyMode.NONE) {
mapOf(
"startButton" to start_button
)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = Dialog(requireContext())
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.setContentView(R.layout.start_dialog)
displayButtons(Wrapper(requireContext())
return dialog
}
private fun displayButtons(wrapper: Wrapper) {
wrapper.queryButtons(object : Wrapper.OnQueryButtonListener {
override fun onSuccess(buttons: List<ButtonDetails>) {
buttons.forEach { button ->
buttonsMap[button.key]?.apply {
text = "${button.description}"
setOnClickListener {
wrapper.startProcess(requireActivity(), button)
}
}
}
}
})
}
}
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/start_button"
android:layout_width="300dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:gravity="center"
android:textColor="#color/ef_white"
android:text="Start"
android:textSize="16sp">

Is there a way that dynamic Link can carry values or intents can be included in them

So I'm using kotlin to build an app of places and I have implemented a share button using dynamic links to share each place and when clicked, the app will open to that specific place, skipping the splash activity. It was working fine before but I modify my code to use one activity for opening all this different places using intent.putExtra(), and retrieving it with getStringExtra(). So now the dynamic links are opening a blank activity because the information is only retrieve with getStringExtra() and the dynamic links do not carry those values. Is there a way to implement those values to tthe dynamic links? I have tried many ways and I don't seem to find the solution.
<activity
android:name=".food.Category"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!--Drinks-CoffeeShops-->
<data android:host="myapp.page.link" android:scheme="https" android:path="/Drinks/CoffeeShops/1/" />
<data android:host="myapp.page.link" android:scheme="https" android:path="/Drinks/CoffeeShops/2/" />
...
<!--Food-FastFood-->
<data android:host="myapp.page.link" android:scheme="https" android:path="/Food/FastFood/1/" />
<data android:host="myapp.page.link" android:scheme="https" android:path="/Food/FastFood/2/" />
...
</intent-filter>
</activity>
This is my android manifest and the dynamic links are sharing exactly how they are intended to be.
override fun onStart() {
super.onStart()
checkForDynamicLinks()
}
private fun checkForDynamicLinks() {
FirebaseDynamicLinks.getInstance().getDynamicLink(intent).addOnSuccessListener {
Log.i("Share", "We have a dynamic link!")
var deepLink: Uri? = null
if (it != null) {
deepLink = it.link
}
if (deepLink != null) {
Log.i("Share" , "Here's the deep link Url:\n" +
deepLink.toString())
}
}.addOnFailureListener {
Log.i("Share", "Oops, we couldn't retrieve dynamic link data")
}
}
private fun generateSharingLink(
deepLink: Uri ,
getShareableLink: (String) -> Unit = {} ,
) {
val dynamicLink = FirebaseDynamicLinks.getInstance().createDynamicLink().run {
link = deepLink
domainUriPrefix = "https://myapp.page.link/"
androidParameters {
build()
}
buildDynamicLink()
}
getShareableLink.invoke(dynamicLink.uri.toString())
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
//Drinks-CoffeeShops
val coffeeShops = intent?.getStringExtra("DrinksCoffeeShops").toString()
//Food-FastFood
val foodFastFood = intent?.getStringExtra("FoodFastFood").toString()
if (id == R.id.shareButton){
generateSharingLink(
deepLink =
when {
//Drinks-CoffeeShops
coffeeShops.toIntOrNull() in (1..50) -> {
"https://myapp.page.link/Drinks/CoffeeShops/$coffeeShops/".toUri()
}
//Food-FastFood
fastFood.toIntOrNull() in (1..50) -> {
"https://myapp.page.link/Food/FastFood/$fastFood/".toUri() }
else -> { "https://myapp.page.link/".toUri() }
}
) { generatedLink ->
// Use this generated Link to share via Intent
shareDeepLink(generatedLink)
}
}
}
}
return super.onOptionsItemSelected(item)
}
private fun shareDeepLink(deepLink: String) {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_SUBJECT , "Share")
intent.putExtra(Intent.EXTRA_TEXT , deepLink)
val app = intent.resolveActivity(packageManager)
val unsupportedAction = ComponentName.unflattenFromString("com.android.fallback/.Fallback")
if (app != null && app != unsupportedAction) try {
val chooser = Intent.createChooser(intent , "Share Using:")
startActivity(chooser)
return
} catch (ignored: ActivityNotFoundException) {
}
Toast.makeText(this , "Required App not Installed" , Toast.LENGTH_LONG).show()
}
So I figure a way to assign an intent to the incoming dynamic link and if someone is also looking for a similar solution I will share what I did.
So I had to retrieve the parameters of the link by using:
val deepLink = if (Firebase.dynamicLinks.getDynamicLink(intent).isSuccessful){
Firebase.dynamicLinks.getDynamicLink(intent).result.link
}else{
Log.e("TAG", "No incoming link")
}
Notice that I used if() else() statement, that's because If I didn't the activity will always expect a link to open, and if I tried to normally use the app without a link, it was going to crash.
After retrieving the dynamic link I have to know which link was clicked to give each one of them values. This is an example of my link:
https://myapp.page.link/Category/1/ or https://myapp.page.link/SubCategory/1/
So to make the specific link do something I simply made an expression like:
if("Category" in deepLink){
//do something
}
if("SubCategory" in deepLink){
//do something
}
Hope this helps!

Cannot fill a MutableLiveData of type ArrayList, outcome is always null

Im working on a quizgame and i want to store some ids in a MutableLiveData-arraylist. Therfore i made a function to loop all my documents in de database and add each ID to the arraylist. BUT the outcome is always null. I don't see where i go wrong?
I'm working with a MVVM-structure
GameViewModel:
class GameViewModel : ViewModel() {
// database instance
val db = FirebaseFirestore.getInstance()
// the current category
private val _category = MutableLiveData<String>()
val category: LiveData<String>
get() = _category
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>()
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
// the current question
private val _question = MutableLiveData<String>()
val question: LiveData<String>
get() = _question
/**
* Set Current Category
*/
fun SetCategory (categoryName: String){
_category.value = categoryName
}
/**
* Get the list of QuestionIds
*/
fun GetListQuestionIds() {
db.collection("questions")
.whereEqualTo("category", "$_category")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
_questionIdsArray.value?.add(document.data["questionid"] as Long)
Log.d("GetSize","${_questionIdsArray.value?.size}")
}
Log.d("GetSize2","${_questionIdsArray.value?.size}")
}
.addOnFailureListener { exception ->
Log.w("errorforloop", "Error getting documents: ", exception)
}
}
/**
* Get a Question
*/
fun GetQuizQuestion() {
Log.d("retro","${_questionIdsArray.value?.size}")
db.collection("questions")
.whereEqualTo("category", "$_category")
.whereEqualTo("questionid", "${_questionIdsArray.value?.get(0)}")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
_question.value = document.data["question"].toString()
}
}
.addOnFailureListener { exception ->
Log.w("err", "Error getting documents: ", exception)
}
}
GAMEFRAGMENT:
class GameFragment : Fragment() {
private lateinit var viewModel: GameViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentGameBinding.inflate(inflater)
// Get the viewModel
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
binding.lifecycleOwner = this
// Set the viewModel for DataBinding - this allows the bound layout access to all of the data in the VieWModel
binding.gameviewModel = viewModel
//arguments passed
val selectedCategory = arguments?.getString("selectedCategory")!!
//set current category so that the viewModel can use it
viewModel.SetCategory(selectedCategory)
viewModel.GetListQuestionIds()
viewModel.GetQuizQuestion()
return binding.root
}
If someone can enlighten me ...
Your Problem
You're not initializing the array. This is your code:
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>()
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
This declares a MutableLiveData of type ArrayList<Long>, but does not initialize it so its value defaults to null.
In your for loop you conditionally add items:
_questionIdsArray.value?.add(document.data["questionid"] as Long)
But of course value was never initialized so it's null so add is no-op (does nothing).
The Solution
Just ensure you initialize the live data object at some point.
You could do this inline in the declaration:
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>(arrayListOf())
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
Or during your attempt to populate the list:
.addOnSuccessListener { documents ->
val idsArray = arrayListOf<Long>() // Non-null list to add to
for (document in documents) {
idsArray.add(document.data["questionid"] as Long)
Log.d("GetSize","${idsArray.size}")
}
_questionIdsArray.value = idsArray // Now set live data with a valid list
Log.d("GetSize2","${_questionIdsArray.value?.size}")
}

DataBinding with LiveData- after return to fragment from popbackstack, UI update not made

I have a viewmodel with following methods:
private fun getCart(): LiveData<MenuCart?> {
return Transformations.switchMap(venueId) { venueId ->
venueId?.let {
repository.getMenuCart(it)
} ?: MutableLiveData<MenuCart?>(null)
}
}
fun getCartQty(): LiveData<Int> {
return Transformations.map(cartVal) {
it?.items?.count() ?: 0
}
}
Also have these fields defined inside view model:
val cartVal = getCart()
val cartQtyVal = getCartQty()
Then inside xml have this inside TextView:
android:text="#{viewModel.cartQtyVal.toString()}"
And data for xml defined as:
<data>
<variable
name="viewModel"
type="mypackage.viewmodels.VenueMealsViewModel" />
</data>
Inside fragment, have this:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val _binding = LayoutVenueMealsMenuBinding.inflate(inflater, container, false)
_binding!!.lifecycleOwner = this
_binding!!.viewModel = this.viewModel
return _binding!!.root
}
I use a similar approach in a few places, and it works. But in this case, I have seen a bug where after navigating to another view, and then returning to this fragment, the UI does not update with the latest value of cartQtyVal. Any ideas why? Since the data binding approach is not working, I am temporarily not use data binding, and instead am observing the live data inside the fragment, which works robustly.
I think the main problem is that when the fragment is re-created after pop backstack, the switchmaps in my view model are not re-triggered. I had to move the setter for the input variable that drives the switchmap to inside the onViewCreated method.

Resources