MutableLiveData 未在 UI 中更新

新手上路,请多包涵

更新: 如果我移动到另一个片段并返回到这个片段,TextView 会更新……

我无法通过使用 setValue() 或 postValue() 使 UI 中的 MutableLiveData 更新为新值。我可以通过将 MutableLiveData 更改为 ObservableData 来使其工作,但重点是使用 LiveData 而不是 ObservableData。

我在其他视图中有类似的事情没有问题。不确定这里发生了什么……我唯一能想到的是我在相机活动(通过意图)和这个片段之间来回跳跃。在活动结果中,我将数据从片段设置到 ViewModel。

我不相信我需要为片段中的值设置任何观察者,因为我在 XML 和 ViewModel 之间有两种方式的数据绑定。

我的片段.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>

我的视图模型.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);
    }
}

现在,由于 LiveData 不保证在更改值时更新 UI,我认为这可能是一个绑定调度问题。那也没有解决问题……

我的片段.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();
        }
    }
}

同样,如果将 photosCount 从 MutableLiveData 切换到 ObservableData,问题就解决了,但这不是 LiveData。

原文由 DevinM 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

我无法通过使用 setValue() 或 postValue() 使 UI 中的 MutableLiveData 更新为新值。我可以通过将 MutableLiveData 更改为 ObservableData 来使其工作,但重点是使用 LiveData 而不是 ObservableData。

您可能没有为您的 binding 对象设置生命周期所有者。从 JAVADOC 对于 setLifecycleOwner() 这个假设听起来更有说服力(请看 这里):

如果 LiveData 位于其中一个绑定表达式中并且未设置 LifecycleOwner,则不会观察到 LiveData,并且不会将对其的更新传播到 UI。

因此,我仔细查看了您的代码。

观察

我注意到您没有显示您的 ViewModel 和绑定是如何创建的。 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 无缝值。然后,我根据我的工作示例填写了您的代码中缺失的部分。请参阅下面的更新代码。

相关代码

主视图模型

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>

主片段

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();
    }
}

结论

请注意,必须为 bindingbinding.setLifecycleOwner(this)MainViewModel 也必须设置为 binding 。否则,它将无法正常工作。如有疑问,请始终在 此处 查看官方文档。

原文由 Anatolii 发布,翻译遵循 CC BY-SA 4.0 许可协议

我不确定这是否是您问题的答案,但正在改变

myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

myViewModel = new ViewModelProvider(requireActivity()).get(MyViewModel.class);

为我做了把戏。根据官方文档,在片段中初始化 ViewModel 时,需要使用 requireActivity()

原文由 Ian 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏