Databinding TextView no data when use apply function to assign lifeCycleOwner - data-binding

The TextView cannot display my data which fetch from network. I am sure that I assign the lifecycleOwner and viewModel in onCreateView but nothing displayed. Then I remove apply function and assign lifecycleOwner line by line and it works but I don't know why. Please tell me if anyone know the reason!
In ViewModel, I have a data class for display
private val _priceData = MutableLiveData<PriceData>()
val priceData: LiveData<PriceData>
get() = _priceData
In Fragment, I assigned the lifecycleOwner and viewModel in onCreateView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// The TextView display nothing using apply
binding = FragmentPriceBinding.inflate(inflater, container, false).apply{
lifecycleOwner = viewLifecycleOwner
viewModel = viewModel
}
// The TextView show the data correctly if I assign lifeCycle line by line
binding = FragmentPriceBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
return binding.root
}
Then I use dataBinding in xml TextView to observe liveData
<data>
<variable
name="viewModel"
type="com.yuyu.gasprice.price.PriceViewModel" />
</data>
<TextView
android:id="#+id/gasoline_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#{viewModel.priceData.predict}" />

You assign your binding.viewModel by binding.getViewModel() . I think you want to assign the viewModel which init from fragment right?
You should replace
viewModel = viewModel
with
viewModel = this#YourFragment.viewModel

Related

Type mismatch: inferred type is Model but List<Model> was expectedd

I'm using the room library in a Mvvm architecture application.
There is a main piece and a detail piece.
I go to the detail fragment with id. So I pull the ID from the room. But I will use recyclerview in detail fragment. As you know, there is a list in the adapter class. I am also pulling objects from livedata. These two do not overlap. Do you have a suggestion about it?
This is the detail trailer.
I go to the detail fragment by clicking on Home Fragment. Of course, the relevant information in the list I clicked on will be in the detail section. Think of it like a news site. But I'm going to extract the identity from the elaborate fragmentary room. But I will use recylerview in the detail trailer, but pull it out of the room. The ID and list on the adapter do not match.
Output of the error = Type mismatch: inferred type is Int but List was expected
private var uuidd=0
private lateinit var Biewmodell:Bilimdetayviewmodel
private lateinit var bindingdetay:FragmentBdetayBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
uuidd = BdetayFragmentArgs.fromBundle(it).uuidd
}
val viewmodelll:Bilimdetayviewmodel by viewModels()
Biewmodell=viewmodelll
Biewmodell.roomgit(uuidd)
observer()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
bindingdetay=DataBindingUtil.inflate(inflater,R.layout.fragment_bdetay, container, false)
return bindingdetay.root
}
fun observer(){
Biewmodell.getroomlivedata.observe(viewLifecycleOwner, Observer { model ->
model?.let {
val adapterr = dAdapterd(requireContext(),it)
bindingdetay.adapterr=adapterr
}
})
}

Populate a RecyclerView in a bottom nav fragment from firebase realtime database

I want to populate a recycler view with a realtime firebase database query. The recycler´s fragment is on a bottom nav (the second destiny). The layout:
<TextView
android:id="#+id/tv_id_owner_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_menu_owner"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv_id_owner_menu"
app:layout_constraintVertical_bias="1.0" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/btn_add_item_menu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:contentDescription="#string/add_item"
android:src="#drawable/ic_add_item"
app:layout_constraintBottom_toBottomOf="#+id/rv_menu_owner"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
In my gradle:
implementation 'com.google.firebase:firebase-database:19.7.0'
implementation 'com.firebaseui:firebase-ui-database:2.3.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
kapt "androidx.lifecycle:lifecycle-compiler:2.3.1"
// ViewModel Kotlin support
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
//Koin
implementation "org.koin:koin-android:2.0.1"
implementation "org.koin:koin-androidx-viewmodel:2.0.1"
//Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
The code in fragment:
class MenuOwnerFragment : Fragment() {
private var _binding: FragmentMenuOwnerBinding? = null
private val binding get() = _binding!!
private val firebaseOwnerViewModel: FirebaseOwnerViewModel by inject()
lateinit var ownersMenuDb: DatabaseReference
private lateinit var menuRecyclerView: RecyclerView
private lateinit var firebaseRecyclerAdapter: FirebaseRecyclerAdapter<MenuOwner, MenusViewHolder>
private val coroutineScope = CoroutineScope(Dispatchers.Main)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = FragmentMenuOwnerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
firebaseOwnerViewModel.currentOwnerLD.observe(viewLifecycleOwner, Observer {
binding.tvIdOwnerMenu.text = it.id
})
val ownerId = binding.tvIdOwnerMenu.text.toString()
menuRecyclerView = view.findViewById(R.id.rv_menu_owner)
menuRecyclerView.layoutManager = LinearLayoutManager(requireContext())
loadFirebaseData(ownerId)
binding.btnAddItemMenu.setOnClickListener { view ->
findNavController().navigate(R.id.action_navigation_menu_owner_to_itemMenuFragment)
}
}
private fun loadFirebaseData(ownerId: String) {
ownersMenuDb = FirebaseDatabase.getInstance().getReference("Menus").child(ownerId)
firebaseRecyclerAdapter = object : FirebaseRecyclerAdapter<MenuOwner, MenusViewHolder>(
MenuOwner::class.java,
R.layout.menu_row,
MenusViewHolder::class.java,
ownersMenuDb
) {
override fun populateViewHolder(viewHolder: MenusViewHolder?, model: MenuOwner?, p2: Int) {
viewHolder?.binding?.itemName?.text = model?.itemName
viewHolder?.binding?.itemDescription?.text = model?.itemDescription
viewHolder?.binding?.itemPrice?.text = model?.itemPrice.toString()
Glide.with(requireContext()).load(model?.itemPhoto).into(viewHolder!!.binding.itemImage)
}
}
menuRecyclerView.adapter = firebaseRecyclerAdapter
}
class MenusViewHolder(mview: View) : RecyclerView.ViewHolder(mview) {
val binding = MenuRowBinding.bind(mview)
}
}
It works and compiles and if I put .child("a value from my firestore child") instead of .child(ownerId), the result it´s what I expect.
With .child(ownerId), the textView shows the right value in the app, but the recycler shows "null". Debuger said "val ownerId = binding.tvIdOwnerMenu.text.toString()" is "". I don´t know what I´m missing because this way:
firebaseOwnerViewModel.currentOwnerLD.observe(viewLifecycleOwner, Observer {
binding.tvIdOwnerMenu.text = it.id
})
of passing parameters works fine for me in other fragments/activities. I´d try with sinthetics (instead of view binding), findViewById, coroutines (to delay loadFirebaseData(ownerId)), all the compile ways with/without parameter ownerId, the code onCreateView, onViewCreated, the FIFA, the UEFA... It´s my first post, sorry if I´m not clear. Any suggestion? Thanks in advance

ListView ID called on null? (Kotlin)

I'm new to Kotlin and im trying to build a HomePage where there is a BottomNavigation with 3 Fragment pages but in 1 of the pages I set a ListView and whenever I call the ID it gives the following errer(Caused by: java.lang.IllegalStateException: categoryListView must not be null)
Here is how Im calling it in my HomePage which is where the content appears:
class HomePage : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
lateinit var adapter : CategoryAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home_page)
adapter = CategoryAdapter(this, DataService.categories)
categoryListView.adapter = adapter
navView.setNavigationItemSelectedListener(this)
bottomNavigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
replaceFragment(HomeFragment())
}
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when(item.itemId) {
R.id.Bottom_Nav_Home -> {
println("Home pressed")
replaceFragment(HomeFragment())
return#OnNavigationItemSelectedListener true
}
R.id.Bottom_Nav_Notifs -> {
println("Notification pressed")
replaceFragment(NotifsFragment())
return#OnNavigationItemSelectedListener true
}
R.id.Bottom_Nav_List -> {
println("List pressed")
replaceFragment(ListFragment())
return#OnNavigationItemSelectedListener true
}
}
false
}
private fun replaceFragment(fragment: Fragment) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.NavPageFragment, fragment)
fragmentTransaction.commit()
}
}
am I doing it correct? or am I supposed to call the categoryListView elsewhere? because I tried in my ListFragment which is where my activity code is and it gave a context error
here is how the Fragment activity looks:
<?xml version="1.0" encoding="utf-8"?>
<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:id="#+id/Bottom_List"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Fragments.ListFragment">
<ListView
android:id="#+id/categoryListView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
You are trying to get the categoryListView resolved inside the activity but it is (as far as I understand) part of the fragment. This simply does not work.
The CategoryAdapter and the ListView should be used inside your HomeFragment class and this will work then.
So move this code into your fragment:
adapter = CategoryAdapter(this, DataService.categories)
categoryListView.adapter = adapter
Update
I would move it into the onViewCreated() method. There you can simply rely on the fact that there is a view available with a context.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = CategoryAdapter(view.context, DataService.categories)
categoryListView.adapter = adapter // categoryListView will be not null here
}

how to replace a fragment in kotlin from a onclicklistener inside a recyclerview

How can I replace the fragment when i click on an item inside a recyclerView
I've tried inside onClickListener of adapter
childFragmentManager
.beginTransaction()
.replace(R.id.framefragment2, Fragment2())
.commit()
with no succes it returns me
java.lang.IllegalArgumentException: No view found for id 0x7f0a0137 (com.test.justaapp:id/framefragment1) for fragment Fragmetnone{1ebd5fc #0 id=0x7f0a0137}
Here is mi xml of 1st fragment already inflated
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/framefragment1"
android:layout_width="match_parent"
android:layout_height="match_parent">
and the second frame layout
<FrameLayout 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:background="#C0C0C0">
<FrameLayout
android:id="#+id/framefragment2"
android:layout_width="match_parent"
android:layout_height="match_parent">
I've been searching for hours and can't get it to work, any ideas? maybe I'm mimssing something very simple, any help would be apreciated Greets.
You can implement an interface on your ViewHolder, then you pass an implementation of this interface to your adapter from your activity. In your adapter your pass the implementation when you create the CustomViewHolder instance (create method).
In your activity/fragment:
private val adapter = YourAdapter(object: CustomViewHolder.Listener {
fun onClick() {
// change the fragment here with the fragment manager of your activity/fragment.
}
})
In your adapter:
class YourAdapter(private val listener: CustomViewHolder.Listener): RecyclerView.Adapter {
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return CustomViewHolder(
LayoutInflater.from(context).inflate(R.layout.view_layout, parent, false),
listener
)
}
}
ViewHolder implementation:
class CustomViewHolder(view: View, private val listener: Listener): ViewHolder(view) {
...
interface Listener {
fun onClick()
}
fun onCreate(parent: View, listener: Listener): CustomViewHolder {
// inflate the view and return an instance of CustomViewHolder
}
fun onBind() {
button.setOnClickListener {
listener.onClick()
}
}
}
I didn't test this code but the logic is here. You just need to adapt it to your project.

MVVMCross Fragment and dynamically setting event commands

I have a MvxFragment that is bound to a view model that has two ICommand properties defined. This fragment contains an MvxListView and can be part of different activities/layouts dependant on the device size/orientation.
What I want to know is how to specify the ItemClick event command of the MvxBind property of the MvxListView dynamically or is there a better way to handle this use case? Should I use a separate fragment?
A similar use case to the one I am trying to achieve is within the Overview section of this Xamarin Dev Guide
View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<MvxListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
local:MvxBind="ItemsSource Courses; ItemClick Comamnd1"
local:MvxItemTemplate="#layout/coursetemplate" />
</LinearLayout>
ViewModel (Simpified)
public class MyViewModel : MvxViewModel
{
private MvxCommand<MyMessage> messageCommand;
public MyViewModel (IMvxMessenger messenger, IHttpClientBuilderService httpClientBuilder)
{
this.httpClientBuilder = httpClientBuilder;
this.messenger = messenger;
}
public ICommand Comamnd1 {
get { return new MvxCommand<Course> ((c) => ShowViewModel<MyOtherViewModel>(c)); }
}
public ICommand Command2 {
get
{
messageCommand = messageCommand ?? new MvxCommand<MyMessage>(c =>
{
var message = new MyMessage(this, c);
messenger.Publish(message);
});
return selectedCourse;
}
}
}
OK, you can do this my overriding the OnActivityCreated event for the fragment as below.
public override void OnActivityCreated (Bundle savedInstanceState)
{
base.OnActivityCreated (savedInstanceState);
MvxListView list = Activity.FindViewById<MvxListView>(Resource.Id.[theID]);
if (Activity.FindViewById<View>(Resource.Id.[fragmentID]) != null)
list.ItemClick = ((MyViewModel)ViewModel).Command1;
else
list.ItemClick = ((MyViewModel)ViewModel).Command2;
}
You can pull out the list by using the Activity.FindViewById function and then set the appropraite ICommand or IMvxCommand from the ViewModel via the list.ItemClick event

Resources