Search code examples
androidkotlinandroid-fragmentsandroid-databindingmobile-application

How to Intialize the Binding Properties in the Fragment to Make Two Way Data Binding Work


So I'm doing a project and I'm completely lost. I have seen how to do data binding with TextViews but I am being asked to do it with EditText Views with Two Way Data Binding. I got up to here so far with it.

The XML File.

<?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="myShoe"
            type="com.udacity.shoestore.product.Shoe" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary">

        <TextView
            android:id="@+id/title_detail_view"
            style="@style/title_style"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="80dp"
            android:text="@string/add_shoe_title"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/shoe_name"
            style="@style/login_style"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:ems="10"
            android:hint="@string/shoe_name_string"
            android:inputType="text"
            android:textSize="30sp"
            android:text="@={myShoe.name}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/title_detail_view" />

        <EditText
            android:id="@+id/shoe_size"
            style="@style/login_style"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/size_string"
            android:inputType="number|numberDecimal"
            android:textSize="15sp"
            android:text="@={myShoe.size}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/shoe_name" />

        <EditText
            android:id="@+id/company_name"
            style="@style/login_style"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/company_string"
            android:inputType="text"
            android:textSize="15sp"
            android:text="@={myShoe.company}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/shoe_size" />

        <EditText
            android:id="@+id/shoe_description"
            style="@style/login_style"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/description_string"
            android:inputType="text"
            android:textSize="15sp"
            android:text="@={myShoe.description}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/company_name" />

        <Button
            android:id="@+id/cancel_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:backgroundTint="@color/colorPrimaryDark"
            android:text="@string/cancel_string"
            android:textColor="@android:color/white"
            app:layout_constraintBaseline_toBaselineOf="@+id/savee_button"
            app:layout_constraintEnd_toStartOf="@+id/savee_button"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/savee_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="88dp"
            android:backgroundTint="@color/colorPrimaryDark"
            android:text="@string/save_string"
            android:textColor="@android:color/white"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/shoe_description" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

I was told to implement it to a fragment and it should work. But I'm not sure how. Here's the fragment

class ShoeDetailsFragment : Fragment() {

    private val viewModel: ActivityViewModel by activityViewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {


        val binding: FragmentShoeDetailsBinding = DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_shoe_details,
            container, false
        )



        //initializing the button and clearing the views once canceled
        binding.cancelButton.setOnClickListener { v: View ->
            v.findNavController().navigateUp()

            binding.shoeName.text.clear()
            binding.shoeSize.text.clear()
            binding.companyName.text.clear()
            binding.shoeDescription.text.clear()

        }
        //initializing the button and saving the info to transfer to the shoeList
        binding.saveeButton.setOnClickListener { v: View ->
            v.findNavController().navigateUp()

            val name = shoe_name.text.toString()
            val size = shoe_size.text.toString()
            val brand = company_name.text.toString()
            val details = shoe_description.text.toString()
            viewModel.addShoe(name, size, brand, details)
        }

        return binding.root
    }
}

I am open to any ideas to initialize binding properties so i can use it in both the layout and the fragment. Or am I looking at this the wrong way?

P.S. The XML File is being represented in this fragment


Solution

  • I also did this project for my NanoDegree.
    Inside my ViewModel I created 3 variables for each EditText:

    • MutableLiveData - to update the value inside the viewModel

    • LiveData to expose value outside viewModel e.g. in a fragment (you won't really need this)

    • Public Variable to monitor value of MutableLiveData and expose this to your xml thus achieving the 2-Way Binding.

    Then I and would create a Shared ViewModel to share data between ShoeDetailsFragment and ShoeListingFragment .

    Inside the SharedViewModel

    I created 3 variables for each EditText (this is just the first 2 Edittexts):

    class MySharedViewModel : ViewModel() {
     private val _name = MutableLiveData<String>()
        val name: LiveData<String>
            get() = _name
        var edShoeName = ""
    
        private val _size = MutableLiveData<Double>()
        val size: LiveData<Double>
            get() = _size
        var edSize = ""
    
    ......}
    

    For the xml I did exactly what you have done but used the 3rd variable for the 2-Way Data Binding:

    <EditText
                android:id="@+id/shoe_name"
                style="@style/login_style"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:hint="@string/shoe_name_string"
                android:inputType="text"
                android:textSize="30sp"
                android:text="@={mySharedViewModel.edShoeName}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/title_detail_view" />
    
            <EditText
                android:id="@+id/shoe_size"
                style="@style/login_style"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="@string/size_string"
                android:inputType="number|numberDecimal"
                android:textSize="15sp"
                android:text="@={mySharedViewModel.edCompany}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/shoe_name" />
    

    I have seen you have included this line of code on your ShoeDetailFragment code:

     binding.saveeButton.setOnClickListener { v: View -> ....}
    

    In my case I did it inside SharedViewModel instead:

    fun onSaveButtonClick() {
    
            //check value entered for size and set it to 0 if blank
            if (edSize == "") {
    
                edSize = "0"
            }
    
           
    
            //update MutableLiveData with values read live from the EditText
            _name.value = edShoeName
            _size.value = edSize.toDouble()
            
    
            //save shoeObject to the _shoeList MutableLiveData
            _shoeList.value?.add(Shoe(edShoeName, edSize.toDouble(), edCompany, edDescription))
    
        }
    

    Using DataBinding I moved the onClick bit to xml:

    <Button
            android:id="@+id/buttonSave"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="75dp"
            android:layout_marginBottom="75dp"
            android:background="@drawable/custom_button_background"
            android:onClick="@{()->sharedViewModel.onSaveButtonClick()}"
    

    You can also refer to my project.