数据绑定 recyclerview 适配器从 livedata 接收到空列表

Posted

技术标签:

【中文标题】数据绑定 recyclerview 适配器从 livedata 接收到空列表【英文标题】:databinding recyclerview adapter received null list from livedata 【发布时间】:2019-08-19 06:19:19 【问题描述】:

我想为 recyclerview 提供元素列表和实时数据。我使用了数据绑定适配器,当我想向适配器发送数据时,列表从 livedata 返回 null。整个代码如下。 谢谢

我调试了代码,我看到数据何时来自 AppDbHelper.java 到 HomeViewmodel.java 并设置为绑定适配器未更改的实时数据。因此,虽然 livedata 不为空,但数据绑定适配器(heartRateResultsModels)处的数组列表为空,导致应用程序崩溃。

1.fragment_home.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="viewModel"
            type="ir.basamadazmanovin.heartrate.ui.main.home.HomeViewModel" />
    </data>

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout"
        android:layout_
        android:layout_
        app:refreshing="@viewModel.isLoading">

        <FrameLayout
            android:layout_
            android:layout_
            tools:context=".ui.main.home.HomeFragment">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/fragment_home_recyclerView"
                android:layout_
                android:layout_
                app:adapter="@viewModel.heartRateResultsLiveData"
                tools:listitem="@layout/row_fragment_home" />


        </FrameLayout>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</layout>

2.BaseFragment.java

public abstract class BaseFragment<T extends ViewDataBinding, V extends BaseViewModel> extends Fragment 

    protected FragmentNavigation mFragmentNavigation;
    private BaseActivity mActivity;
    private View mRootView;
    private T mViewDataBinding;
    private V mViewModel;

    public abstract int getBindingVariable();

    public abstract
    @LayoutRes
    int getLayoutId();

    public abstract V getViewModel();

    @Override
    public void onAttach(Context context) 
        super.onAttach(context);
        if (context instanceof BaseActivity) 
            BaseActivity activity = (BaseActivity) context;
            this.mActivity = activity;
            activity.onFragmentAttached();
        
        if (context instanceof FragmentNavigation) 
            mFragmentNavigation = (FragmentNavigation) context;
         else 
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        
    

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        performDependencyInjection();
        super.onCreate(savedInstanceState);
        mViewModel = getViewModel();
        setHasOptionsMenu(false);
    

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
        mRootView = mViewDataBinding.getRoot();
        return mRootView;
    

    public BaseActivity getBaseActivity() 
        return mActivity;
    

    public T getViewDataBinding() 
        return mViewDataBinding;
    

    public void hideKeyboard() 
        if (mActivity != null) 
            mActivity.hideKeyboard();
        
    

    public boolean isNetworkConnected() 
        return mActivity != null && mActivity.isNetworkConnected();
    

    public void openActivityOnTokenExpire() 
        if (mActivity != null) 
            mActivity.openActivityOnTokenExpire();
        
    

    private void performDependencyInjection() 
        AndroidSupportInjection.inject(this);
    

    public interface Callback 

        void onFragmentAttached();

        void onFragmentDetached(String tag);
    

3.HomeFragment.java

public class HomeFragment extends BaseFragment<FragmentHomeBinding,HomeViewModel> implements HomeNavigator 

    FragmentHomeBinding mFragmentHomeBinding;
    @Inject
    LinearLayoutManager mLayoutManager;
    @Inject
    HeartRateResultsAdapter mAdapter;
    @Inject
    ViewModelProviderFactory factory;




    private HomeViewModel mHomeViewModel;

    public static HomeFragment newInstance() 
        return new HomeFragment();
    

    @Override
    public int getBindingVariable() 
        return BR.viewModel;
    

    @Override
    public int getLayoutId() 
        return R.layout.fragment_home;
    

    @Override
    public HomeViewModel getViewModel() 
        mHomeViewModel = ViewModelProviders.of(this, factory).get(HomeViewModel.class);
        return null;
    

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mHomeViewModel.setNavigator(this);

    

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);
        mFragmentHomeBinding = getViewDataBinding();
        setUp();
    

    private void setUp() 
        mLayoutManager.setOrientation(RecyclerView.VERTICAL);
        mFragmentHomeBinding.fragmentHomeRecyclerView.setLayoutManager(mLayoutManager);
        mFragmentHomeBinding.fragmentHomeRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mFragmentHomeBinding.fragmentHomeRecyclerView.setAdapter(mAdapter);
    

    @Override
    public void handleError(Throwable throwable) 

    


4.BaseViewModel.java

public class BaseViewModel<N> extends ViewModel 
    private final DataManager mDataManager;

    private final ObservableBoolean mIsLoading = new ObservableBoolean(false);

    private final SchedulerProvider mSchedulerProvider;

    private CompositeDisposable mCompositeDisposable;

    private WeakReference<N> mNavigator;

    public BaseViewModel(DataManager dataManager,
                         SchedulerProvider schedulerProvider) 
        this.mDataManager = dataManager;
        this.mSchedulerProvider = schedulerProvider;
        this.mCompositeDisposable = new CompositeDisposable();
    

    @Override
    protected void onCleared() 
        mCompositeDisposable.dispose();
        super.onCleared();
    

    public CompositeDisposable getCompositeDisposable() 
        return mCompositeDisposable;
    

    public DataManager getDataManager() 
        return mDataManager;
    

    public ObservableBoolean getIsLoading() 
        return mIsLoading;
    

    public void setIsLoading(boolean isLoading) 
        mIsLoading.set(isLoading);
    

    public N getNavigator() 
        return mNavigator.get();
    

    public void setNavigator(N navigator) 
        this.mNavigator = new WeakReference<>(navigator);
    

    public SchedulerProvider getSchedulerProvider() 
        return mSchedulerProvider;
    


5.HomeViewModel.java

public class HomeViewModel extends BaseViewModel<HomeNavigator> 
    private  MutableLiveData<ArrayList<HeartRateResultsModel>> heartRateResultsLiveData;

    public HomeViewModel(DataManager dataManager, SchedulerProvider schedulerProvider) 
        super(dataManager, schedulerProvider);
        heartRateResultsLiveData = new MutableLiveData<>();
        fetchRepos();
    
    public void fetchRepos() 
        setIsLoading(true);
        getCompositeDisposable().add(getDataManager()
                .getHeartRateResults()
                .subscribeOn(getSchedulerProvider().io())
                .observeOn(getSchedulerProvider().ui())
                .subscribe(heartRateResultsModels -> 
                    heartRateResultsLiveData.setValue(heartRateResultsModels);
                    setIsLoading(false);
                , throwable -> 
                    setIsLoading(false);
                    getNavigator().handleError(throwable);
                ));
    

    public LiveData<ArrayList<HeartRateResultsModel>> getHeartRateResultsLiveData() 
        return heartRateResultsLiveData;
    

6.BindingUtils.java

public final class BindingUtils 

    private BindingUtils() 
        // This class is not publicly instantiable
    


    @BindingAdapter("imageUrl")
    public static void setImageUrl(ImageView imageView, String url) 
        Context context = imageView.getContext();
        Glide.with(context).load(url).into(imageView);
    

    @BindingAdapter("onNavigationItemSelected")
    public static void setOnNavigationItemSelectedListener(
            BottomNavigationView view, OnNavigationItemSelectedListener listener) 
        view.setOnNavigationItemSelectedListener(listener);
    


    @BindingAdapter("adapter")
    public static void addHeartRateResultsItems(RecyclerView recyclerView,
                                                ArrayList<HeartRateResultsModel> heartRateResultsModels) 
        HeartRateResultsAdapter adapter = (HeartRateResultsAdapter) recyclerView.getAdapter();
        if (adapter != null) 
            adapter.clearItems();
            adapter.addItems(heartRateResultsModels);
        
    

7.DataManager.java

public interface DataManager extends DbHelper, PreferencesHelper, ApiHelper 


8.DbHelper.java

public interface DbHelper 
    Single<ArrayList<HeartRateResultsModel>> getHeartRateResults();


9.AppDataManager.java

public class AppDataManager implements DataManager

    private final ApiHelper mApiHelper;

    private final Context mContext;

    private final DbHelper mDbHelper;

    private final Gson mGson;

    private final PreferencesHelper mPreferencesHelper;

    @Inject
    public AppDataManager(Context context,
                          DbHelper dbHelper,
                          PreferencesHelper preferencesHelper,
                          ApiHelper apiHelper,
                          Gson gson) 
        mContext = context;
        mDbHelper = dbHelper;
        mPreferencesHelper = preferencesHelper;
        mApiHelper = apiHelper;
        mGson = gson;
    

    @Override
    public Single<ArrayList<HeartRateResultsModel>> getHeartRateResults() 
        return mDbHelper.getHeartRateResults();
    



10.AppDbHelper.java

@Singleton
public class AppDbHelper implements DbHelper


    private final AppDatabase mAppDatabase;

    @Inject
    public AppDbHelper(AppDatabase appDatabase) 
        this.mAppDatabase = appDatabase;

    



    @Override
    public Single<ArrayList<HeartRateResultsModel>> getHeartRateResults() 
        return Single.fromCallable(new Callable<ArrayList<HeartRateResultsModel>>() 
            @Override
            public ArrayList<HeartRateResultsModel> call() throws Exception 
                ArrayList<HeartRateResultsModel> models = new ArrayList<>();
                models.add(new HeartRateResultsModel("","1"));
                models.add(new HeartRateResultsModel("","2"));
                return models;
            
        );
    


【问题讨论】:

【参考方案1】:

我错过了设置

mViewDataBinding.vm = viewModel

其中“vm”是布局中的变量,“viewModel”是活动或片段中的局部变量

此外,正如@Menma 建议的那样,不要忘记添加

mViewDataBinding.setLifecycleOwner(this);

编辑: 如果视图在可见性逻辑方面没有更新,请确保分配了“id”属性(尤其是在包含布局时)

【讨论】:

【参考方案2】:

在方法onViewCreated() 上添加mViewDataBinding.setLifecycleOwner(this); BaseFragment,像这样:

@Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);
        mViewDataBinding.setVariable(getBindingVariable(), mViewModel);
        mViewDataBinding.setLifecycleOwner(this);
        mViewDataBinding.executePendingBindings();
    

【讨论】:

【参考方案3】:

1。始终确保在 UI 控制器(片段或活动)中设置 XML 绑定变量

在您的 XML 布局中

<data>
<variable
    name="viewModel"
    type="com.felixfavour.ViewModel" />
</data>

在您的 UI 控制器中(本例中为 Fragment)

listViewModel = ViewModel(this).get(ListViewModel::class)
bindingObj.viewModel = listViewModel

2。有必要将 setLifecycleOwner() 设置为 LifeCycleOwner,这本质上是您的 UI 控制器,否则您的 LiveData 将失去其 Live 效果,因为它无法被观察到,本质上您的 LiveData 将不会传播到用户界面。

bindingObj.setLifeCycleOwner = this

this = UI 控制器(同样是 Activity/Fragment)

【讨论】:

以上是关于数据绑定 recyclerview 适配器从 livedata 接收到空列表的主要内容,如果未能解决你的问题,请参考以下文章

使用数据绑定的具有多个视图类型的 Android recyclerview 适配器

如何使用 Kotlin 在 RecyclerView Adapter 中实现 onClick 并进行数据绑定

ANDROID 如何通过适配器将 mutableLiveData 绑定到我的 Recyclerview

使用数据绑定填充 RecyclerView 内的 MultiView ViewHolder

Kotlin 中 RecyclerView 中项目绑定的逻辑

Android:重新绑定 ListView 或 RecyclerView 而不刷新 Header