How to use a MutableLiveData Object in my xml? - android

I have an object that will fill some fields in my xml, but a mutablelivedata return a T value and I can't use the T properties. What I need to do to use the T object in my xml?
I don't want to create a mutablelivedata for each object member.
Sorry for my bad english..
class ExampleFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewModel = ViewModelProviders.of(this).get(MyVm::class.java)
val binding = DataBindingUtil.inflate<FragmentExampleBinding>(
inflater,
R.layout.fragment_house,
container,
false
)
binding.myVm = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
class MyVm: ViewModel(){
val house: MutableLiveData<House> by lazy { MutableLiveData<House>().apply{ value =
House().apply{
door = "First door"
window = "My window"
}
} }
val thing: MutableLiveData<String> by lazy { MutableLiveData<String>().apply{ value = "something" } }
}
class House(){
var door: String = ""
var window: String = ""
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="my_vm" type="br.com.MyVm"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
orientation="vertical"
>
<!--I need this work-->
<TextView
android:layout_width="wrap_parent"
android:layout_height="wrap_parent"
android:text="my_vm.house.door"
/>
<!--I need this work-->
<TextView
android:layout_width="wrap_parent"
android:layout_height="wrap_parent"
android:text="my_vm.house.window"
/>
<TextView
android:layout_width="wrap_parent"
android:layout_height="wrap_parent"
android:text="my_vm.thing"
/>
</LinearLayout>
<layout>

This code is correcty! A tried run and worked, I thounght it didn't work because the android studio didn't show the attributes when press ctrl + space.

Related

Using data bindings in Fragments

I have a problem using viewmodel in fragment with data binding.
In my programm i'm using only one activity and fragments. I organized transitions between fragments using navigation. And i want to use data binding in my fragment, but when running the application only with binding, everything works well when I pass to the binding viewmodel when switching to this fragment of the application, it crashes.
I would be very grateful for your help!
My fragment
class TimeSettings : Fragment() {
private lateinit var viewModel: TimeSettingsViewModel
private lateinit var binding: FragmentTimeSettingsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentTimeSettingsBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.viewmodel = viewModel;
return binding.root
}
}
Object for viewmodel
object TimeData {
var inhale: Int = 4
private val _currentTimeInhale = MutableLiveData<Int>()
val currentTimeInhale: LiveData<Int>
get() = _currentTimeInhale
init{
_currentTimeInhale.value = inhale
}
fun changeCurrentTimeInhale(){
_currentTimeInhale.value = 5
}
}
Viewmodel
class TimeSettingsViewModel : ViewModel() {
val currentInhale: LiveData<Int>
get() = TimeData.currentTimeInhale
fun onChange() = TimeData.changeCurrentTimeInhale()
}
xml file for fragment
<?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="viewmodel"
type="ivakin.first.my_first_app.viewmodels.TimeSettingsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView"
android:layout_width="95dp"
android:layout_height="59dp"
android:text="#{viewmodel.currentInhale}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.452" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Set"
android:onClick="#{()->viewmodel.onChange()"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
U Forgot Initialize ViewModel :
viewmodel = TimeSettingsViewModel()
Also dont use return binding.root

StateFlow value displayed using Data Binding not updating

I've switched from using LiveData to StateFlow in my app and also applied it in Data Binding. But after changing the value of StateFlow, the UI is not updating. From what I understand from the StateFlow docs, I only have to assign a new value to the value property of a StateFlow to update its consumers.
Below, I have a time field which I update the value by using an MaterialTimePicker dialog. But after I change the time and assign a new value to the selectedAlarm property (StateFlow) of my viewmodel, the UI is not updating for the new value. I'm not sure where is my mistake or what is lacking.
Below is my AlarmFormFragment:
#AndroidEntryPoint
class AlarmFormFragment : Fragment() {
private val alarmsViewModel: AlarmsViewModel by activityViewModels()
private var _binding: FragmentAlarmFormBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAlarmFormBinding.inflate(inflater, container, false)
binding.alarmsViewModel = alarmsViewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setUpTimeField()
}
private fun setUpTimeField() {
alarmsViewModel.selectedAlarm.value.let { alarm ->
binding.llTimeField.setOnClickListener {
// Create and then show time picker
val picker =
MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setTitleText("Set Time")
.setHour(alarm.time.get(Calendar.HOUR_OF_DAY))
.setMinute(alarm.time.get(Calendar.MINUTE))
.build()
picker.addOnPositiveButtonClickListener {
alarm.time.set(Calendar.HOUR_OF_DAY, picker.hour)
alarm.time.set(Calendar.MINUTE, picker.minute)
alarmsViewModel.selectedAlarm.value = alarm
}
picker.show(childFragmentManager, "TIME_INPUT_DIALOG")
}
}
}
}
Below is the xml file for AlarmFormFragment:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="com.example.myclock.utilities.Utility" />
<import type="com.example.myclock.utilities.SnoozeUtils"/>
<variable
name="alarmsViewModel"
type="com.example.myclock.viewmodels.AlarmsViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AlarmFormFragment">
<!-- Form Fields -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- Time Field -->
<LinearLayout
android:id="#+id/llTimeField"
style="#style/FormFieldLayoutStyle">
<TextView
style="#style/FormFieldTextStyle.Label"
android:text="#string/label_time" />
<TextView
android:id="#+id/tvTime"
style="#style/FormFieldTextStyle.Value"
android:text="#{Utility.INSTANCE.timeToString(alarmsViewModel.selectedAlarm.value.time)}" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And below is my AlarmsViewModel:
#HiltViewModel
class AlarmsViewModel #Inject constructor(
private val alarmsRepository: AlarmsRepository
) : ViewModel() {
private val _alarms = MutableStateFlow<List<Alarm>>(emptyList())
val alarms: StateFlow<List<Alarm>> get() = _alarms
var selectedAlarm = MutableStateFlow(Alarm())
...
}
Below is class for the data Alarm:
#Parcelize
#Entity
class Alarm(
#PrimaryKey(autoGenerate = true) val id: Int = 0,
var time: Calendar = Calendar.getInstance(),
...
) : Parcelable {
...
}

DataBinding doesn't update value when changing LiveData

I want the value to increase when I press the button and write it in the textview.
However, the code below shows the increasing value as Log
But, on screen, the value in textview does not change.
I'm not sure what's wrong. Please let me know if you know.
Fragment.kt
class SettingFragment : Fragment(), View.OnClickListener {
companion object {
fun newInstance() = SettingFragment()
private val TAG = "SettingFragment"
}
private lateinit var viewModel: SettingViewModel
private lateinit var binding: FragmentSettingBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_setting, container, false)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(SettingViewModel::class.java)
binding.lifecycleOwner = this
binding.button.setOnClickListener(this)
}
override fun onClick(v: View?) {
viewModel.increase()
Log.d(TAG, "Log Data" + viewModel.testInt.value)
}
}
FragmentViewModel
class SettingViewModel : ViewModel() {
val testInt: MutableLiveData<Int> = MutableLiveData()
init {
testInt.value = 0
}
fun increase() {
testInt.value = testInt.value?.plus(1)
}
}
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.example.ui.setting.SettingViewModel" />
</data>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.setting.SettingFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{String.valueOf(viewModel.testInt)}" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</layout>
<variable
name="viewModel"
type="com.example.ui.setting.SettingViewModel" />
This viewModel variable doesn't bind to a certain variable programmatically. So, when the value incremented, it won't reflect to the layout.
To fix this set the fragment's viewModel to this value:
viewModel = ViewModelProvider(this).get(SettingViewModel::class.java)
binding.viewModel = viewModel // <<<<< Here is the change
binding.lifecycleOwner = this
binding.button.setOnClickListener(this)
Side note:
You shouldn't use onActivityCreated() as it's is deprecated as of API level 28; you can normally its part of code to onCreateView(). And you can check here for other alternatives.

Data binding from variable in fragment, android?

I have a string variable in my fragment which I want to use in XML with data binding
This is my fragment
class HomeFragment : Fragment() {
var struc: String = "Organization Structure"
lateinit var binding: FragmentHomeBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.getRoot()
}}
This is the 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="data"
type="com.mountmeru.view.HomeFragment" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.HomeFragment">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tvOrganization"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/ivOrganization"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="#{data.struc}"
android:textAlignment="center"
android:textColor="#color/mountmerured"
android:textSize="15sp" />
</FrameLayout></layout>
When I run the application the textview text is never set.
What is wrong here?
First way :
This not direct way which you asked. But you can also do using data class, this is little bit long way but you can add multiple data.
Make data class
data class Data(var struc: String = "Organization Structure")
Add this in xml
<data>
<variable
name="dataModel"
type="Data" /> // here data class path of step1
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.HomeFragment">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tvOrganization"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/ivOrganization"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="#{dataModel.struc}" // use like this
android:textAlignment="center"
android:textColor="#color/mountmerured"
android:textSize="15sp" />
</FrameLayout></layout>
In Activity or fragment
private fun setData() {
val data = Data(struc= "Organization")
bindObject.dataModel = data
}
Second way :
Or Else direct way is
<data>
<variable
name="struc"
type="String" />
</data>
and from activity/fragment
bindObject.struc = "Organization Structure";
Third way :
Or In your case you need to do this in onCreateView and everything works
binding.data = this;
I think you have mistaken while the layout inflating. Please try below code
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
return binding.getRoot()
}}

Button Text Not Changing with ViewModel Changes

I'm trying to change the text of a button based on user interaction. The text is bound to a boolean variable in the view model and is supposed to observe changes in that variable. When the variable changes, the text is supposed to switch. But it's not.
<?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"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="userDataViewModel"
type="com.mysite.myapp.viewModels.UserDataViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/change_group_location_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.GroupLocationFragment">
<Button
android:id="#+id/select_location_button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:text='#{userDataViewModel.userData.groupChanged ? "changed" : "not changed"}'/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Here is the part of the fragment where I bind the view model:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_group_location, container, false)
binding.lifecycleOwner = this
userDataViewModel = activity?.run {
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs, userDataFetcherService))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
userDataViewModel
.getUserData()
.observe(this, Observer {
binding.userDataViewModel = userDataViewModel
})
return binding.root
}
I can see that the boolean variable in the view model toggles to 'true', but the button text still says 'not changed'. I just don't see what I am overlooking.
EDIT
Adding in the ViewModel class in case the issue is there.
class UserDataViewModel(private val prefs: SharedPreferences): ViewModel() {
val userData: MutableLiveData<UserData> by lazy {
MutableLiveData<UserData>().also {
val userDataString = prefs.getString(UserData.USER_DATA_SHARED_PREFERENCE_KEY, "")
it.value = Gson().fromJson(userDataString, UserData::class.java)
}
}
fun getUserData(): LiveData<UserData> {
return userData
}
}
Might be an issue with a button text which you set.
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/change_group_location_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.GroupLocationFragment">
<Button
android:id="#+id/select_location_button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:text='#{userDataViewModel.userData.groupChanged ? `changed` : `not changed`}'/>
change it with back quote (`) sign.
You can use string from string.xml also like below
<Button
android:id="#+id/select_location_button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:text="#{userDataViewModel.userData.groupChanged ? #string/txt_changed : #string/txt_not/-changed}"

Categories

Resources