Search code examples
androidandroid-databindingandroid-livedatamutablelivedata

DataBinding & LiveData : two implementations (Kotlin & Java) and can't make the Java impl working


I'm facing issues while using DataBinding and LiveData in a Java projet. I followed a previous course in Kotlin and when I try to implement the same behaviors I just can't make it work. I'm clearly missing something in terms of understanding so I'd like to have you thoughts.

I'll paste the code from the Kotlin (working) example and then the Java (not working) one.

KOTLIN

score_fragment.xml

...
<data>

    <variable
        name="scoreViewModel"
        type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/score_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".screens.score.ScoreFragment">

    <TextView
        android:id="@+id/score_text"
        ...
        android:text="@{String.valueOf(scoreViewModel.score)}"
        .../>
...

ScoreFragment.kt

class ScoreFragment : Fragment() {

private lateinit var viewModelFactory: ScoreViewModelFactory
private lateinit var viewModel: ScoreViewModel

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

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.score_fragment,
                container,
                false
        )

        // Get args using by navArgs property delegate
        val scoreFragmentArgs by navArgs<ScoreFragmentArgs>()

        viewModelFactory = ScoreViewModelFactory(scoreFragmentArgs.score)
        viewModel = ViewModelProviders.of(this, viewModelFactory)
                .get(ScoreViewModel::class.java)

        binding.scoreViewModel = viewModel
        binding.lifecycleOwner = this

        return binding.root
    }
}

ScoreViewModel.kt

class ScoreViewModel(finalScore: Int) : ViewModel() {

    private val _score = MutableLiveData<Int>()
    val score: LiveData<Int>
        get() = _score

    private val _eventPlayAgain = MutableLiveData<Boolean>()
    val eventPlayAgain: LiveData<Boolean>
        get() = _eventPlayAgain

    init {
        Timber.i("ScoreViewModel created")
        _score.value = finalScore
    }

    fun onPlayAgain() {
        _eventPlayAgain.value = true
    }

    fun onPlayAgainComplete() {
        _eventPlayAgain.value = false
    }

    override fun onCleared() {
        super.onCleared()
        Timber.i("ScoreViewModel cleared")
    }
}

Explanations : let's focus only on the score value. In ScoreViewModel the value is of type LiveData. When the fragment's launched, the value is correctly displayed on the screen through "@{String.valueOf(scoreViewModel.score)}". This works correctly.

JAVA

activity_main.xml

<data>
    <variable
        name="noteViewModel"
        type="com.example.architectureapp.viewModel.NoteViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textview"
        android:text="@{noteViewModel.test}" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private NoteViewModel mNoteViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        mNoteViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);
        binding.setNoteViewModel(mNoteViewModel);
        binding.setLifecycleOwner(this);
    }
}

NoteViewModel

public class NoteViewModel extends AndroidViewModel {
    public MutableLiveData<String> test = new MutableLiveData<>("TesT");

    public NoteViewModel(@NonNull Application application) {
        super(application);
    }
}

Explanations : here I'm setting a MutableLiveData test whith a value of "TesT" and then I intent to display it using android:text="@{noteViewModel.test}". But the text is never displayed and remains blank.

Obviously there is something wrong but despite the syntaxic differences between the two implementations I just can't figure out why the Java version is not displaying the value in the Textview.

EDIT

Thanks to Rajnish suryavanshi I was not getting my binding the right way, I had to only use :

binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

This one set the content view with the layout provided AND return the binding. Alternatively you can do :

binding = ActivityMainBinding.inflate(getLayoutInflater()); (returns the binding but does not set the content view)

setContentView(binding.getRoot()); (set the content view with the binding root view)

I found this misleading -> https://developer.android.com/topic/libraries/data-binding/expressions

It states that we can replace

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

by

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

which is not the same !

Happy to get the subtility now !


Solution

  • Take a look on this two lines.

    setContentView(R.layout.activity_main);
    binding = ActivityMainBinding.inflate(getLayoutInflater());
    

    You are not creating your binding while inflating the layout. Instead of setContentView use DataBindingUtil.setContentView(this, R.layout.activity_main);

    And now you can get the view using layout inflater

    binding = ActivityMainBinding.inflate(getLayoutInflater());