I went through couple of examples but either they have too many abstractions or out of date tutorials. I would like to implement this feature in the minimum possible way. I need help with the UserForm class here with User passed as arguments in a Navigational Architecture.
Module app build.gradle I've added this
android {
...
dataBinding {
enabled = true
}
}
User class
#Parcelize
data class User(var first: String, var last: String): Parcelable
FormFragment.kt
class UserForm: Fragment() {
private val user by lazy {
arguments?.getParcelable("user") ?: User("John", "Doe")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_form, container, false)
}
}
fragment_form.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">
<data>
<variable name="user" type="data.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/first"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:text="#{user.first}" />
<EditText
android:id="#+id/last"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:text="#{user.last}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Some snippets
modify your code below:
FormFragment.kt
class UserForm: Fragment() {
private val user by lazy {
arguments?.getParcelable("user") ?: User("John", "Doe")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentFormBinding.inflate(inflater, container, false).apply {
user = this#UserForm.user
}
return binding.root
}
}
Explanation:
Because you used DataBinding in fragment_form.xml, the corresponding FragmentFormBinding class will be automatically generated
We usually use XxxBinding.inflate instead of inflater.inflate (layoutid, container, false)
After FragmentFormBinding.inflate, we bind the user object to fragment_form.xml
Related
I've done my research on memory leak when using Data Binding. Every posts said that one should assign null to binding object to resolve this issue. However, it just doesn't work.
I've created two identical Fragments with a single Activity. Each Fragment includes a button which can be used to navigate to one another.
To reproduce this issue, click the button repeatedly, LeakCanary will pop up the memory leak warning. (However, the issue does not occur when clicking the buttons "programmatically")
Repro
LeakCanary
Code
The following is the code of my Fragments. (Two fragments are identical)
FirstFragment.kt
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentFirstBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
binding.btnFirst.setOnClickListener {
findNavController().navigate(R.id.action_to_second)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
fragment_first.xml
<?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>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/first_fragment" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="To second" />
</FrameLayout>
</layout>
SecondFragment.kt
class SecondFragment : Fragment() {
private var _binding: FragmentSecondBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentSecondBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
binding.btnSecond.setOnClickListener {
findNavController().navigate(R.id.action_to_first)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
fragment_second.xml
<?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>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/second_fragment" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="To first" />
</FrameLayout>
</layout>
I've also created a repo in case someone want to try it out.
Let me know if I can provide any further information.
Thanks!
To all whom may have run into this issue, the developer team of leakcanaray has found the source and marked the leak as known issue.
github.com/square/leakcanary/issues/2341
A related issue can be found on Google issue tracker.
I am trying to create modal bottom sheet dialog to show a list of items which will be scrollable.
dialog_bottom_example.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="#+id/container"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Items in LinearLayout is getting added dynamically based on list size as below:
ExampleDialogBottom:
class ExampleDialogBottom: BottomSheetDialogFragment() {
private lateinit var binding: DialogExampleBinding
private var skusItems = mutableListOf<SkuItem>();
companion object {
internal const val TAG = "ExampleDialogBottom"
#JvmStatic
fun newInstance(nonEligibleErxItem: NonEligibleErxItem) = NonEligibleBottomDialog
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DialogExampleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
configureSkuItems()
}
private fun configureSkuItems() {
if (!skuItems.isNullOrEmpty()) {
val inflater = LayoutInflater.from(context)
for (sku in skuItems) {
val skuItemView = SingleSkuItemViewBinding.inflate(inflater, null, false)
skuItemView.skuImage.setImageWithVisibility(sku.imageUrl)
skuItemView.skuTitle.setTextWithVisibility(sku.name)
binding.container.addView(skuItemView.root)
}
binding.container.visibility = View.VISIBLE
} else {
binding.container.visibility = View.GONE
}
}
ExmpaleActivity:
button.setOnClickListener {
ExampleDialogBottom.newInstance().show(getSupportFragmentManager(),
ExampleDialogBottom.TAG);
}
However, when I click on button, a transparent dialog shows up, no content is showing.
When I removed match-contraint and changed it match-parent or wrap-content, it's working as expected.
It would be great if someone could help? Thanks in advance.
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.
I'm trying to use Data binding to run a function when an onClick event occurs, I'm hoping someone can tell me what I'm doing wrong here.
The log item in myClick doesn't run.
XML
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="myBinding"
type="com.example.deletebindingtest.MyFragment" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyFragment"
android:orientation="vertical">
<Button
android:id="#+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/button"
android:onClick="#{(view) -> myBinding.myClick()}"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
My Fragment
class MyFragment : Fragment() {
private lateinit var binding: FragmentMyBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_my,
container,
false
)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
fun myClick() {
Log.i("TEST", "Its working")
}
}
When I click on the extension in the XML it takes me to myClick function.
a Quick fix is on onCreateView add binding.myBinding=this
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_my,
container,
false
)
binding.lifecycleOwner = viewLifecycleOwner
binding.myBinding=this // here
return binding.root
}
also I prefer listener binding you can check it here
I have the following code for my layout and my fragment:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ScanFragment">
<SurfaceView
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</layout>
class ScanFragment : Fragment() {
private lateinit var binding: FragmentScanBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
FragmentScanBinding.inflate(inflater, container, false).also {
binding = it
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.cameraPreview
}
}
However, I can't seem to access the cameraPreview view directly from the binding. I have already added the pre-requisites for DataBinding below:
dataBinding {
enabled = true
}
and
android.databinding.enableV2=true
inside the gradle.properties
You should use DataBindingUtil
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_scan, container, false)
return binding.root