Initializing data binding variable in corresponding fragment - android

I have been working through the Android Basics in Kotlin Course available on developer.android.com and have ran into a problem with Data Binding. The project I am working on doesn't have solution code provided, but I have been modeling my approach off of a previous similar Codelab.
I am attempting to initialize data binding variables declared in layout xml files in the fragments corresponding to each layout but when I attempt to initialize the fragment variable I receive an error: "Classifier 'EntreeMenuFragment' does not have a companion object, and thus must be initialized here". build.Gradle has both dataBinding and viewBinding set to true.
<layout
...
<data>
<variable
name="viewModel"
type="com.example.lunchtray.model.OrderViewModel" />
<variable
name="EntreeMenuFragment"
type="com.example.lunchtray.ui.order.EntreeMenuFragment" />
</data>
...
</layout>
class EntreeMenuFragment : Fragment() {
private var _binding: FragmentEntreeMenuBinding? = null
private val binding get() = _binding!!
private val sharedViewModel: OrderViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentEntreeMenuBinding.inflate(inflater, container, false)
val root: View = binding.root
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
EntreeMenuFragment = this#EntreeMenuFragment // ERROR
}
}
....

It was sufficient to remove capitalization on the binding variable in order to avoid the error:
<variable
name="entreeMenuFragment"
type="com.example.lunchtray.ui.order.EntreeMenuFragment" />
entreeMenuFragment = this#EntreeMenuFragment // fixed

Related

Data binding& fragment issue android

I'm now trying to implement data binding for a fragment in my android studio project. I followed all the guidelines provided in official android documentation.
It builds well, but when I launch it on my emulator It throw this compilation error
Cannot inherit from final 'com.example....databinding.FragmentFoldersBinding'
FragmentFoldersBinding()' has private access in 'com.example...databinding.FragmentFoldersBinding'`
`Cannot resolve symbol 'mAdapter'.
Cannot resolve symbol 'mViewModel'
Here is my fragment class code
#AndroidEntryPoint
class FoldersFragment : Fragment(), FoldersAdapter.FolderClickListener {
private lateinit var binding : FragmentFoldersBinding
private val vm : FoldersViewModel by activityViewModels()
private val adapter : FoldersAdapter by lazy { FoldersAdapter(this as FoldersAdapter.FolderClickListener) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentFoldersBinding.inflate(inflater)
binding.apply {
viewModel = vm
lifecycleOwner = this#FoldersFragment
adapter = adapter
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)
binding.addFolderButton.setOnClickListener {
FolderAddDialog().show(childFragmentManager, null)
}
binding.foldersRecyclerView.isNestedScrollingEnabled = false
binding.foldersRecyclerView.layoutManager = LinearLayoutManager(view.context)
vm.foldersLiveData.observe(viewLifecycleOwner){
adapter.setData(it)
}
}
override fun onClickedFolder(folder : Folder) {
val action = FoldersFragmentDirections.actionFoldersFragmentToNotesFragment(folder)
findNavController().navigate(action)
}
override fun setUpOnItemSwiped(swipe: SwipeCallback) {
val itemTouchHelper = ItemTouchHelper(swipe)
itemTouchHelper.attachToRecyclerView(binding.foldersRecyclerView)
}
override fun onDeleteSwiped(folder: Folder) {
vm.deleteFolder(folder)
}
}
fragment xml part
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example....presentation.folders_feature.FoldersViewModel" />
<variable
name="adapter"
type="com.example...presentation.folders_feature.FoldersAdapter" />
</data>
I've tried multiple of different solutions suggested. Thank you for helping in advance!

How to assign viewModel instance to binding viewModel data variable within fragment, if i need to pass context to ViewModel?

I will try to explain my issue step by step.
I created data variable within xml file
<data>
<variable
name="SLC90RViewModel"
type="com.example.poy.ui.questionnaires.tools.SCL90RViewModel"/>
</data>
In my ViewModel i need to retrieve string array from resources (which is stored in xml file), so i passed app context into ViewModel constructor. Now it looks like this:
class SCL90RViewModel(val context: Context) : ViewModel() {
val item = context.getResources().getStringArray(R.array.firstArray)
}
Finally, in my Fragment i want to assign LifecycleOwner and ViewModel to the binding:
class QuestionnaireFragment : Fragment(R.layout.fragment_questionnaire) {
private var binding: FragmentQuestionnaireBinding? = null
private val scl90rViewModel: SCL90RViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentBinding = FragmentQuestionnaireBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
SCL90RViewModel = scl90rViewModel
}
}
}
However, SCL90RViewModel in binding?.apply{} is outlined with red and message "Classifier 'SCL90RViewModel' does not have a companion object, and thus must be initialized here" pops up. How can i fix it?
SCL90RViewModel is ambiguous here, it can be the binding variable or the ViewModel class. So use this inside the apply block to refer to the binding variable.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
this.SCL90RViewModel = scl90rViewModel
}

Android Data Binding. Button onClick not working

I am stuck here, help.
I've got the following code:
ProfileFragment:
#AndroidEntryPoint
class ProfileFragment : Fragment() {
private val profileViewModel: ProfileViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.viewModel = profileViewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
}
ProfileViewModel:
class ProfileViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val profileRepository: ProfileRepository
) : ViewModel() {
fun getUser() {
....
}
}
fragment_profile.xml:
<data>
<variable
name="viewModel"
type="my.app.viewmodel.ProfileViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="55dp"
android:onClick="#{()->viewModel.getUser()}" />
</LinearLayout>
The problem is that the onClick is never triggered no matter what I do and how I try.
However, as soon as I do it like this in ProfileFragment it works just fine:
binding.myButton.setOnClickListener {
profileViewModel.getUser()
}
Any ideas? Cause I am stuck here
I am not sure if this issue still exist or not
but if you are using data binding and hilt for dependency injection please add below lines in your fragment onViewCreated
binding.lifecycleOwner = this
binding.viewModel = profileViewModel
What fixed this problem for me was adding the fragment reference to itself in onCreateView:
binding.<fragment_name> = this
Like so:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.v(TAG, "onCreateView")
binding = FragmentSettingsBinding.inflate(inflater, container, false)
binding.settingsFragment = this <---
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}

DataBinding showing null value on first remote call

In my app, i have a fragment that call a remote service for get user profile information and show it, and I've used DataBinding for show data.
This is my layout:
<?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">
<data>
<variable
name="viewModel"
type="com.myapp.ProfileViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.profile.firstName+ ' '+ viewModel.profile.lastName}" />
<!-- Other textviews -->
</LinearLayout>
</layout>
this is ProfileViewModel class
class ProfileViewModel : ViewModel() {
#Inject
lateinit var profileRepository: ProfileRepository
private var _profile = MutableLiveData<Profile>()
val profile: LiveData<Profile>
get() = _profile
fun getProfile(token: String) {
profileRepository.profile(
token,
{
// success
_profile.value = it.value
},
{
//error
}
)
}
}
data class Profile(
firstName : String,
lastName : String,
// other fields
)
And this is fragment where profile should be showed:
class ProfileFragment : Fragment() {
private lateinit var binding: FragmentProfileBinding
private lateinit var viewModel: ProfileViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_profile,
container,
false
)
viewModel = activity?.run {
ViewModelProviders.of(this)[ProfileViewModel::class.java]
} ?: throw Exception("Invalid Activity")
binding.viewModel = viewModel
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getProfile(
"aToken"
)
}
}
Now it happen that first time i open fragment, repository call service and get data correctly, but "null" is showed inside textviews. If i close fragment and i reopen it, edittext are populate correctly. What's wrong with my code?
Set binding.lifecycleOwner in your fragment class in order for updates in LiveData objects to be reflected in corresponding views. Your fragment class should look like this:
class ProfileFragment : Fragment() {
private lateinit var binding: FragmentProfileBinding
private lateinit var viewModel: ProfileViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_profile,
container,
false
)
viewModel = activity?.run {
ViewModelProviders.of(this)[ProfileViewModel::class.java]
} ?: throw Exception("Invalid Activity")
binding.viewModel = viewModel
//add lifecycleOwner
binding.lifecycleOwner = this
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getProfile(
"aToken"
)
}
}

Data Binding inflate layout inside another layout

im trying to make a base bottom sheet dialog fragment class that supports data binding. here is my class:
abstract class RoundedBottomSheetDialogFragment<VM : BaseViewModel, DB : ViewDataBinding> :
BottomSheetDialogFragment() {
abstract val viewModel: VM
open lateinit var binding: DB
private fun init(inflater: LayoutInflater, container: ViewGroup?) {
binding = DataBindingUtil.inflate(inflater, getLayoutRes(), container, true)
}
abstract fun getLayoutRes(): Int
abstract fun configEvents()
abstract fun bindObservables()
/**
*
* You need override this method.
* And you need to set viewModel to binding: binding.viewModel = viewModel
*
*/
abstract fun initBinding()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val parentLayout = inflater.inflate(R.layout.rounded_bottom_sheet, container, false)
init(inflater, parentLayout.container)
showDialogAsExpanded()
return parentLayout
}
private fun showDialogAsExpanded() {
dialog?.setOnShowListener {
val bottomSheetInternal =
(dialog as BottomSheetDialog).findViewById<View>(R.id.design_bottom_sheet) ?: return#setOnShowListener
val behavior = BottomSheetBehavior.from(bottomSheetInternal)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
configEvents()
bindObservables()
}
}
if you see i'm inflating a layout inside this dialog fragment class and i want to use data binding inside that layout xml file.
this is an example of my xml file:
<layout>
<data>
<variable
name="vm"
type="com.mobtakerteam.walleto.ui.login.searchcountry.SearchCountryViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/search_list"
android:layout_width="match_parent"
android:layout_height="400dp"
app:data="#{vm.countries}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="20"
tools:listitem="#layout/fragment_search_country_row" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
but the problem is that it is not working in the xml layout and i have to manually observe the live data objects inside kotlin class like this:
viewModel.countries.observe(this, Observer {
adapter.submitList(it)
})
so what is the problem?
Using LiveData with binding you have to set lifecycle owner like this
binding.lifecycleOwner = this

Categories

Resources