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.
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.
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.
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 !
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());