UPDATE: If i move to another fragment and return to this one the TextView gets updated...
I am unable to get the MutableLiveData in the UI to update to a new value either by using setValue() or postValue(). I can get this to work by changing the MutableLiveData to ObservableData but the point is to use LiveData not ObservableData.
I have similar things working in other views without an issue. Unsure whats happening here... The only thing I can think of is that I am jumping back and forth between a camera activity (via intent) and this fragment. On the activity result I am setting data from the fragment to the ViewModel.
I dont believe that I need to set any observer for the value in the fragment since i have 2 way databinding between the XML and ViewModel.
my_fragment.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="myViewModel"
type="com.example.myapplication.MyViewModel" />
</data>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={myViewModel.photosCount}"
tools:text="1" />
<Button
android:id="@+id/btnTakePhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Take Picture"
android:onClick="@{myViewModel::navClicked}"/>
</LinearLayout>
</layout>
MyViewModel.java
private MutableLiveData<String> photosCount = new MutableLiveData<>();
private MutableLiveData<Boolean> takePhoto = new MutableLiveData<>();
public MyViewModel() {
photosCount.setValue("0"); // This correctly sets the value to "0" in UI
takePhoto.setValue(true);
}
public MutableLiveData<String> getPhotosCount() {
return photosCount;
}
public MutableLiveData<Boolean> getTakePhoto() {
return takePhoto;
}
public void storeImage() {
....
Log.d(TAG, "Updating UI count to: " + String.valueOf(count)); // this shows the correct value that should be updated in the UI
photosCount.setValue(String.valueOf(count));
Log.d(TAG, "Updated value: " + photosCount.getValue()); // this shows the correct updated value
...
}
public void navClicked(@NonNull View view) {
if (view.getId() == R.id.btnTakePhoto) {
takePhoto.setValue(true);
}
}
Now, since LiveData does not guarantee updating the UI at the time of changing the value I thought this might be a binding dispatch issue. That hasent resolved the issue either...
MyFragment.java
private MyViewModel myViewModel;
MyFragmentBinding binding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
binding = DataBindingUtil.inflate(inflater, R.layout.my_fragment, container, false);
binding.setMyViewModel(myViewModel);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
viewModel.getTakePhoto().observe(getViewLifecycleOwner(), takePhoto -> {
if (takePhoto) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
File photo = viewModel.createImageFile();
if (photo != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity(),"com.example.fileprovider", photo);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
this.startActivityForResult(intent, Constants.RequestCodes.MY_REQUEST_IMAGE_CAPTURE);
}
}
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == Constants.RequestCodes.MY_REQUEST_IMAGE_CAPTURE) {
viewModel.storeImage();
}
}
}
Again, If you switch photosCount from MutableLiveData to ObservableData the problem is fixed however this is not LiveData.
I am unable to get the MutableLiveData in the UI to update to a new value either by using setValue() or postValue(). I can get this to work by changing the MutableLiveData to ObservableData but the point is to use LiveData not ObservableData.
You probably don't set a lifecycle owner for your binding
object. From the JAVADOC
for setLifecycleOwner()
this assumption sounds even more convincing (please see it here):
If a LiveData is in one of the binding expressions and no LifecycleOwner is set, the LiveData will not be observed and updates to it will not be propagated to the UI.
So, I took a closer look at your code.
I noticed that you don't show how your ViewModel
and binding are created. So, I have created a simple project meeting your requirements and updating the UI properly - setValue
or postValue
of LiveData
were changing the corresponding View
values seamlessly. Then, I filled in the missing pieces in your code based on my working example. Please see the updated code below.
MainViewModel
public class MainViewModel extends ViewModel {
private MutableLiveData<String> photosCount = new MutableLiveData<>();
private MutableLiveData<Boolean> takePhoto = new MutableLiveData<>();
public MainViewModel() {
photosCount.setValue("0");
takePhoto.setValue(true);
}
public MutableLiveData<String> getPhotosCount() {
return photosCount;
}
public MutableLiveData<Boolean> getTakePhoto() {
return takePhoto;
}
public void createImageFile() {
...
photosCount.setValue(String.valueOf(count));
...
}
...
}
fragment_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="MainViewModel"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={viewModel.photosCount}" />
</LinearLayout>
</layout>
MainFragment
public class MainFragment extends Fragment {
private MainViewModel mainViewModel;
private FragmentMainBinding binding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container,
false);
binding.setLifecycleOwner(this);
mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
binding.setViewModel(mainViewModel);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mainViewModel.getTakePhoto().observe(getViewLifecycleOwner(), takePhoto -> {
if (takePhoto) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
File photo = mainViewModel.storeImage();
if (photo != null) {
Uri photoURI = FileProvider.getUriForFile(getActivity(),"com.example.fileprovider", photo);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
this.startActivityForResult(intent, 0);
}
}
}
});
binding.executePendingBindings();
}
}
Please note that a lifecycle owner must be set for binding
as binding.setLifecycleOwner(this)
. MainViewModel
must be set for binding
too. Otherwise, it won't work properly. When in doubt, always check the official documentation here.