I'm learning kotlin and android architecture components. I have a simple use case of a map toggle button on a google map.
I want to use data binding to bind the map toggle button label to a MutableLiveData field in my ViewModel.
I set the mapType val in the MapViewModel from the onCreate method in the Activity. If I understand correctly, this should trigger the mapLabel val to change due to the use of Transformations.map.
Its not working... Why?
Here's my versions:
MapViewModel.kt
class MapViewModel : ViewModel() {
val mapType: MutableLiveData<MapType> = MutableLiveData()
val mapLabel: LiveData<String> = Transformations.map(mapType, {
if (it == MapType.MAP) "SAT" else "MAP"
})
}
enum class MapType {
SAT, MAP
}
activity_maps.xml
<?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">
<data>
<variable
name="vm"
type="uk.co.oliverdelange.wcr_android_kt.ui.map.MapViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MapsActivity">
<Button
android:id="@+id/map_toggle"
style="@style/Wcr_MapToggle"
android:layout_marginTop="110dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="7dp"
android:layout_gravity="top|end"
android:text="@{vm.mapLabel}" />
</fragment>
</FrameLayout>
</layout>
MapsActivity.kt
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var viewModel: MapViewModel
private lateinit var binding: ActivityMapsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
viewModel = ViewModelProviders.of(this).get(MapViewModel::class.java)
binding.vm = viewModel
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// I can do it this way, but I don't want to.
// viewModel.mapLabel.observe(this, Observer { map_toggle.text = it })
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
}
}
I've tested the binding with a MutableLiveData object where i set the string in the activity, and it works fine. The problem seems to be with the Transformations.map - have i just understood it wrong?
Also, whilst debugging, i see that the mapType val in my ViewModel has no observers (not sure if this is right or wrong, just interesting)
The issue here was that despite being bound to the mapLabel
field, the view binding wasn't being updated when the value of the mapLabel
field changed.
The reason is that I didn't set the lifecycle owner on the binding.
binding.setLifecycleOwner(this)
I realised my mistake after reading this blog post for the 10th time.
My new MapsActivity.kt
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var viewModel: MapViewModel
private lateinit var binding: ActivityMapsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
binding.setLifecycleOwner(this) //<- NEW!
viewModel = ViewModelProviders.of(this).get(MapViewModel::class.java)
binding.vm = viewModel
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
}
}
On the plus side I learned a lot about how LiveData works internally!
Its not working... Why?
When you change the value of mapType
, the value of mapLabel
doesn't change immediately. You need to set value of mapLabel
again. For ex, after you set value for mapType
, you call method to update value of mapLabel
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
viewModel.updateMapLabel()
Your viewModel:
fun updateMapLabel() {
mapLabel.value =
if (it == MapType.MAP) "SAT" else "MAP"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With