I have following branch in Bitbucket : https://bitbucket.org/ali-rezaei/tmdb/src/dataBinding/
I get following Kotlin compiler error when I build the project :
e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding errors.
Error is related to :
app:visibleGone="#{isLoaded}"
in the following layout :
<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="isLoaded"
type="boolean" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:visibleGone="#{isLoaded}" />
</android.support.v4.widget.SwipeRefreshLayout>
<include
layout="#layout/network_state_item"
app:visibleGone="#{!isLoaded}" />
</FrameLayout>
</layout>
I appreciate if you can help me out.
The changes I would do are: Here
<variable
name="isLoaded"
type="boolean" />
Instead of passing boolean I would pass an instance of your VM
<variable
name="vm"
type="com.sample.android.tmdb.ui.MovieViewModel" />
in your fragment, you do
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
mBinding?.setVariable(BR.vm, mViewModel)
mBinding?.setLifecycleOwner(this)
this way, your VM is connected to the lifecycle of your fragment.
Declare a method
#BindingAdapter("visibleGone")
fun View.visibleGone(visible: Boolean) {
setVisibility(if (visible) View.VISIBLE else View.GONE)
}
declare a LiveData<Boolean> variable in you MovieViewModel and connect it in your layout. Eg.
val loading: LiveData<Boolean>
then in your layout you could have
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:visibleGone="#{!safeUnbox(vm.loading)}" />
I had a slightly different problem that generated this error.
In my ViewModel, I had the following method:
`fun onSkip() {
_score.value = (_score.value)?.minus(1)
nextWord()
}`
Now, when i was setting the onClick attributes in my xml, I set them like this:
android:onClick="#{() -> gameViewModel.onSkip}"
instead of
android:onClick="#{() -> gameViewModel.onSkip()}"
Notice how I forgot to use the parenthesis in the first method.
Related
Below code is the XML for the recyclerview item.
How to transfer android:onClick="#{() -> sumListener.onClick(sum)}"(last line) from XML to kotlin?
I would like to make a onLongClick, but XML has no onLongClick.
Please let me know if need more info.
<data>
<variable
name="sum"
type="com.cementcaibird.astock.database.Sum" />
<variable
name="sumListener"
type="com.cementcaibird.astock.main.SumListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/ll_item_container"
android:onClick="#{() -> sumListener.onClick(sum)}">
...(other view code)
It can be achieved with this method
<data>
<variable
name="sum"
type="com.cementcaibird.astock.database.Sum" />
<variable
name="sumListener"
type="com.cementcaibird.astock.main.SumListener" />
</data>
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/ll_item_container">
...(other view code)
In your Activity/Fragment
private lateinit var binding: YourBindingclass
..
binding.linearLayout.setOnClickListener { view->
// Handle your click here
}
binding.linearLayout.setOnLongClickListener{ view->
// Handle your long click here
true
}
Say I have a fragment defined as:
<?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="cpaTabViewModel"
type="org.romco.example.CpaTabViewModel" />
</data>
<LinearLayout
android:id="#+id/fragment_pac_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/testTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{cpaTabViewModel.testString}"
tools:text="test" />
<include
android:id="#+id/addPerformedPAField"
layout="#layout/add_performed_pa"
app:cpaTabViewModel="#{cpaTabViewModel}"/>
[...]
</LinearLayout>
</layout>
The included layout is add_performed_pa.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="cpaTabViewModel"
type="org.romco.example.CpaTabViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
tools:showIn="#layout/cpa_tab_fragment">
<Spinner
android:id="#+id/inputActivitySpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textStart"
app:layout_constraintStart_toEndOf="#+id/cancelButton"
app:layout_constraintTop_toTopOf="#+id/cancelButton"
app:entries="#{cpaTabViewModel.activities}" <---- THIS CAUSES THE PROBLEM
tools:listitem="#layout/sample_textview" />
[...]
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The class representing the fragment is CpaTabFragment.kt:
class CpaTabFragment : Fragment() {
private lateinit var cpaTabFragBinding: CpaTabFragmentBinding
private val cpaTabViewModel: CpaTabViewModel by lazy {
ViewModelProvider(this, CpaTabViewModel.Factory(activity?.application!!))
.get(CpaTabViewModel::class.java)
.apply {
setPeriodType(arguments?.getSerializable(ARG_SECTION_TYPE) as PlanPeriodType)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
cpaTabFragBinding = DataBindingUtil.inflate(inflater, R.layout.cpa_tab_fragment, container, false)
cpaTabFragBinding.testTextView.text = "TEST HEADER"
cpaTabFragBinding.addPerformedPAField.root.visibility = View.GONE
cpaTabFragBinding.cpaTabViewModel = cpaTabViewModel
// (this next line is replaced by passing the viewModel in the <include app:cpaTabViewModel="#{cpaTabViewModel} - I hope this is correct, but that's beside the point here)"
// cpaTabFragBinding.addPerformedPAField.cpaTabViewModel = cpaTabViewModel
return cpaTabFragBinding.root
}
Obviously, I removed most of the unimportant (or at least what I'm hoping is unimportant) code above, leaving only what I think is relevant here.
Without actually using data-binding in the add_performed_pa.xml layout (the line marked with "THIS CAUSES THE PROBLEM"), the entire thing works fine. I can access the included layout as a binding just fine, change it's root visibility, even access any elements in it, etc.
However, as soon as I try to add any data-binding into the included .xml, app won't compile, and the error is the following:
...\app\build\generated\source\kapt\debug\org\romco\example\DataBinderMapperImpl.java:18: error: cannot find symbol
import org.romco.example.databinding.AddPerformedPaBindingImpl;
I can't really find anyone else with this problem. Can you please help?
I will give an example so as to easily explain my problem.
Consider FragmentA with ViewModelA and FragmentB with ViewModelB being used in my app.
I have a BottomSheetSample that is to be used in both FragmentA and FragmentB Since the bottom sheet is doing very little, like selecting a value I want to share the ViewModel of the fragment on which the bottom sheet is being displayed on, instead of having a separate ViewModel and transferring the data to the ViewModel of the fragment. I am binding the ViewModel like this to the nav graph.
Inside FragmentA
private val viewModel: ViewModelA by navGraphViewModels(R.id.nav_A_graph) { viewModelFactory }
Inside BottomSheetSample
private val viewModel: ViewModelA by navGraphViewModels(R.id.nav_A_graph) { viewModelFactory }
I am passing the ViewModel to the XML like this so as to use it with data-binding:
val binding: BottomSheetSampleBinding =
DataBindingUtil.inflate(inflater, R.layout.bottomSheet, container, false)
binding.viewModel = viewModel
sample bottom sheet XML:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmenta.ViewModelA" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()} />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()} />
</LinearLayout>
</layout>
Now the issue is that I have to use the same Bottom Sheet(BottomSheetSample) in FragmentB also. Since I am passing the ViewModel to the XML for data binding, I am not able to use the same bottom sheet as the current BottomSheetSampleBinding is expecting an object of ViewModelA.
So what I end up doing is creating a new BottomSheetSampleB which does exactly the same thing as BottomSheetSample and just change the ViewModel in the new XML to ViewModelB :
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmentb.ViewModelB" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()} />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()} />
</LinearLayout>
</layout>
Now if I have to use the same BottomSheet in a third fragment I have to copy the BottomSheetSample and do the whole thing again. This looks redundant as the same bottom sheet is being replicated again and again. But I don't know how to avoid doing this. Can someone tell me the right way to do this?
Hopefully, I am clear about my issue.
Create a BaseViewModel (Any name) and keep all the things required for bottom sheet in BaseViewModel . Use this BaseViewModel in Bottom Sheet
<layout>
<data>
<variable
name="viewModel"
type="com.ui.fragmentb.BaseViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.oneSelected()}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{()->viewModel.twoSelected()}" />
</LinearLayout>
</layout>
Extend your ViewModelA,ViewModelB with the BaseViewModel
i'm enabling data binding with adding
dataBinding {
enabled = true
}
and
kapt 'com.android.databinding:compiler:3.1.4'
in app level build.gradle file.
apply plugin: 'kotlin-kapt'
is added top of that. project is based on kotlin .
here is my model:
package ir.app.myapplication;
data class cisclass(val equRevId:String)
main Activity :
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val user = cisclass("123")
binding.setVariable(BR.cis, user)
binding.executePendingBindings()
here is my layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="cis"
type="ir.app.myapplication.cisclass" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:orientation="vertical"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:text="#{cis.equRevId}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>
i will got this error:
java.lang.IllegalArgumentException: couldn't make a guess for ir.meedc.myapplication.cisclass
at com.squareup.javapoet.Util.checkArgument(Util.java:64)
at com.squareup.javapoet.ClassName.bestGuess(ClassName.java:171)
at android.databinding.tool.ext.ExtKt.toTypeName(ext.kt:244)
at android.databinding.tool.ext.ExtKt.toTypeName(ext.kt:192)
at android.databinding.tool.ext.ExtKt.toTypeName(ext.kt:173)
at android.databinding.tool.writer.BaseLayoutBinderWriter.createVariableFields(BaseLayoutBinderWriter.kt:229)
at android.databinding.tool.writer.BaseLayoutBinderWriter.access$createVariableFields(BaseLayoutBinderWriter.kt:39)
at android.databinding.tool.writer.BaseLayoutBinderWriter$createType$1.invoke(BaseLayoutBinderWriter.kt:67)
at android.databinding.tool.writer.BaseLayoutBinderWriter$createType$1.invoke(BaseLayoutBinderWriter.kt:39)
at android.databinding.tool.ext.Javapoet_extKt.classSpec(javapoet_ext.kt:39)
at android.databinding.tool.writer.BaseLayoutBinderWriter.createType(BaseLayoutBinderWriter.kt:63)
at android.databinding.tool.writer.BaseLayoutBinderWriter.write(BaseLayoutBinderWriter.kt:59)
at android.databinding.tool.BaseDataBinder.generateAll(BaseDataBinder.kt:65)
at com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask$CodeGenerator.run(DataBindingGenBaseClassesTask.kt:212)
at com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask$writeBaseClasses$$inlined$recordTaskAction$1.invoke(AndroidVariantTask.kt:52)
at com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask$writeBaseClasses$$inlined$recordTaskAction$1.invoke(AndroidVariantTask.kt:31)
at com.android.build.gradle.internal.tasks.Blocks.recordSpan(Blocks.java:91)
how can i fix that? whats the problem?
as mentioned in https://stackoverflow.com/a/50115802/7407809
your model class must be start with uppercase letter.
Try to use instead binding.setVariable(BR.cis, user) this binding.setCis(user)
When I build the project. I'm getting error on app:visibleGone
I'm also enable true to dataBinding in build.gradle and using android architecture components and mvvm.
project targetSdkVersion is 26 and support lib version is 26.0.1.
Below is the error message
error: package com.****.****.databinding does not exist
error: cannot find symbol class ActivityMainBinding
Cannot find the setter for attribute 'app:visibleGone' with parameter type boolean on android.widget.Button.
here is my activity_main.xml
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="loading"
type="boolean" />
</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="com.example.aungmyolwin.importdb.MainActivity">
<Button
android:id="#+id/btn_load_sql"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load from SQL"
app:visibleGone="#{!loading}"/>
<Button
android:id="#+id/btn_load_room"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load from Room mapper"
app:visibleGone="#{!loading}"/>
<TextView
android:id="#+id/tv_import_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Importing database...."
app:visibleGone="#{loading}"/>
</LinearLayout>
</layout>
ActivityMain.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.btnLoadRoom.setOnClickListener(this);
binding.btnLoadSql.setOnClickListener(this);
viewModels= ViewModelProviders.of(this).get(MainActivityViewModels.class);
}
}
You need to create a custom BindingAdapter for app:visibleGone (because it is not a available method).
Like
public class BindingAdapters {
#BindingAdapter("visibleGone")
public static void showHide(View view, boolean show) {
view.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
Moreover, if you don't want to define a method like this, you can do like
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="loading"
type="boolean" />
<import type="android.view.View"/> <!-- remember to import -->
</data>
<LinearLayout >
<Button
android:visibility="#{loading ? View.GONE : View.VISIBLE}"
</LinearLayout>
</layout>