数据绑定 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