获取 LiveData 的初始值总是返回 Null

Getting Initial Value for LiveData Always Returning Null

我正在尝试在应用程序启动时从本地房间数据库加载 loggedInUser。如果之前保存的用户的 Authentication Token 仍然有效,我想跳过提示用户登录!

所以,从 DAO,我想 return 一个 LiveData 对象包含 之前登录用户,然后观察它以进行后续更改。我面临的挑战是,如果我将结果包装在 LiveData[=68 中,则获取当前登录用户 的方法总是 returns null =],但如果 return 编辑为 POJO,则它 return 是预期的用户。

如何强制 LiveData 运行 同步 只是 initialize 然后监听后续变化?我真的想结合这两种行为,因为身份验证可能 无效 后台同步任务 或当 用户注销时(这些操作将替换或更新已保存的令牌,我想在LiveData).

这是我目前尝试过的方法:

AuthorizationDAO.java

public interface AuthorizationDAO {

    @Query("SELECT * FROM Authorization LIMIT 1") //Assume only one Authentication token will exist at any given time
    LiveData<Authorization> getLoggedInUser(); //I want to keep this behaviour

    @Insert(onConflict = REPLACE)
    long insertAuth(Authorization authorization);

    @Update
    void logoutCurrentUser(Authorization authorization);


}

AuthorizationRepository.java

public class AuthorizationRepository {
    private AuthorizationDAO mAuthorizationDAO;

    private MutableLiveData<Authorization> mAuthorization = new MutableLiveData<>();

    public AuthorizationRepository(Application application){

        AppDatabase db = AppDatabase.getDatabase(application);

        this.mAuthorizationDAO = db.mAuthorizationDAO();
    }

    public LiveData<Authorization> getLoggedInUser(){
               mAuthorization.postValue(mAuthorizationDAO.getLoggedInUser().getValue()); //this is always null at startup
        return this.mAuthorization;
    }

AuthorizationViewModel.java

public class AuthorizationViewModel extends AndroidViewModel {

    private AuthorizationRepository mAuthorizationRepository;

    private LiveData<Resource<Authorization>> mAuthorization;
    private LiveData<Authorization> loggedInUserAuth;

    public AuthorizationViewModel(@NonNull Application application) {
        super(application);
        this.mAuthorizationRepository = new AuthorizationRepository(application);

    }
    public void init(){
        this.loggedInUserAuth = this.mAuthorizationRepository.getLoggedInUser();
    }
    public LiveData<Authorization> getLoggedInUserAuth() {
        return this.loggedInUserAuth;
    }
}

AppActivity.java

public class AppActivity extends AppCompatActivity {
    public AuthorizationViewModel mAuthorizationViewModel;
    public  @Nullable Authorization mAuthorization;
    private NavController mNavController;
    private NavHostFragment mNavHostFragment;
    private BottomNavigationView mBottomNavigationView;
    private boolean mIsLoggedIn;
    private ActivityAppBinding mBinding;
    private boolean mIsTokenExpired;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_app);

        mNavHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.app_nav_host_fragment);
        mNavController = mNavHostFragment.getNavController();

        mBottomNavigationView = findViewById(R.id.nav_bottom_nav_view);
        NavigationUI.setupWithNavController(mBottomNavigationView, mNavController);

        if (Build.VERSION.SDK_INT>9){

            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }

        mAuthorizationViewModel = ViewModelProviders.of(this).get(AuthorizationViewModel.class);

        mAuthorizationViewModel.init(); //Here I want to load user synchronously before the rest happens and then on next line observe the same object

        mAuthorizationViewModel.getLoggedInUserAuth().observe(this, new Observer<Authorization>() {
            @Override
            public void onChanged(@Nullable Authorization authorization) {
                mBinding.setViewModel(authorization);
                mIsLoggedIn = authorization == null? false: authorization.isLoggedIn();
                mIsTokenExpired = authorization == null ? true : authorization.isTokenExpired();
                if(!mIsLoggedIn || mIsTokenExpired){
                    if (authorization != null){

                        Log.i("CurrentAuth", "mIsLoggedIn?: "+authorization.isLoggedIn());
                        Log.i("CurrentAuth", "isTokenExpired?: "+authorization.isTokenExpired());
                        Log.i("CurrentAuth", "tokenCurrentTime?: "+ Calendar.getInstance().getTime());
                        Log.i("CurrentAuth", "tokenIssuedAt?: "+ authorization.getIat());
                        Log.i("CurrentAuth", "tokenExpiresAt?: "+ authorization.getExp());
                    }
                    mNavController.navigate(R.id.start_login);
                }
            }
        });

如您所见,我正在调用 mAuthorizationViewModel.init() 这样我就可以 加载或初始化 loggedInUserAuth 来自 本地数据库 ,然后 observe 相同的 LiveData 实例mAuthorizationViewModel.getLoggedInUserAuth().observe() 在下一行!但是 loggedInUserAuth 的值 returned 是 always null

请帮忙,谢谢!

在classAuthorizationRepository

中创建mAuthorization的getter方法
public MutableLiveData<Authorization> getAuthorizationResult() {
   return mAuthorization;
}

然后像下面这样修改你的AuthorizationViewModelclass

public void init() {
    mAuthorizationRepository.getLoggedInUser();
}

public LiveData<Authorization> getLoggedInUserAuth() {
    return mAuthorizationRepository.getAuthorizationResult();
}

在@Krishna 的大力帮助下,我终于解决了这个问题,这里是要点:

  1. DAO 方法 应该return LiveData
  2. Repository class 中,创建一个 LiveData 私有成员变量 而不是 MutableLiveData(这是因为我们将通过 updates/inserts 改变数据库记录)。成员变量将保存对 LiveData 对象的引用 return 由 DAO 方法
  3. 编辑
  4. Repository的构造函数中,初始化 LiveData对象到结果return由 DAO 方法编辑。这样每次activity启动时,都会加载当前保存的记录
  5. 存储库 class 中,创建一个 getter,它将公开 LiveData 对象到 ViewModel
  6. ViewModel class 中,创建一个将 LiveData 对象公开给 的方法视图控制器(activity 或片段)
  7. Activity 或 Fragment 中,只需收听或订阅LiveData[ 上的变化=72=] 由 ViewModel
  8. 提供的 Accessor Method 公开
  9. DAO 也可以公开一个方法来更新 LiveData,允许 Repository通过 ViewModel 启用 Activity 或 Fragment 将更新发送到 LiveData,同时保持所有听众反应!

这里是这个场景的工作代码:

AuthorizationDAO.java

public interface AuthorizationDAO {

    @Query("SELECT * FROM Authorization LIMIT 1") //Assume only one Authentication token will exist at any given time
    LiveData<Authorization> getLoggedInUser(); //I want to keep this behaviour

    @Insert(onConflict = REPLACE)
    long insertAuth(Authorization authorization);

    @Update
    void logoutCurrentUser(Authorization authorization); //this will be used to toggle login status by Activity or Fragment
}

AuthorizationRepository.java

public class AuthorizationRepository {
    private AuthorizationDAO mAuthorizationDAO;
    private AuthorizationWebAPI mAuthorizationWebAPI;
    private LiveData<Authorization> mAuthorization; //reference to returned LiveData

    public AuthorizationRepository(Application application){
        AppDatabase db = AppDatabase.getDatabase(application);
        this.mAuthorizationDAO = db.mAuthorizationDAO();
        this.mAuthorization = mAuthorizationDAO.getLoggedInUser(); //initialize LiveData
    }
    public LiveData<Authorization> getAuthorizationResult() { //getter exposing LiveData
        return mAuthorization;
    }
    public void logoutCurrentUser(){ //toggle login status
        if (this.mAuthorization != null){
            AppExecutors.getInstance().getDiskIO().execute(()->{
                Authorization mAuthorizationObj = this.mAuthorization.getValue();
                mAuthorizationObj.setLoggedIn(false);
                mAuthorizationDAO.logoutCurrentUser(mAuthorizationObj); //update LiveData and changes will be broadcast to all listeners
            });
        }
    }
}

AuthorizationViewModel.java

public class AuthorizationViewModel extends AndroidViewModel {

    private AuthorizationRepository mAuthorizationRepository;

    public AuthorizationViewModel(@NonNull Application application) {
        super(application);
        this.mAuthorizationRepository = new AuthorizationRepository(application);
    }
    public LiveData<Authorization> getLoggedInUserAuth() { //exposes LiveData to the Activity or Fragment
        return mAuthorizationRepository.getAuthorizationResult();
    }
    public void logoutCurrentUser(){ //allows activity or fragment to toggle login status
        this.mAuthorizationRepository.logoutCurrentUser();
    }
}

AppActivity.java

public class AppActivity extends AppCompatActivity {
    public AuthorizationViewModel mAuthorizationViewModel;
    public  @Nullable Authorization mAuthorization;
    private NavController mNavController;
    private NavHostFragment mNavHostFragment;
    private BottomNavigationView mBottomNavigationView;
    private boolean mIsLoggedIn;
    private ActivityAppBinding mBinding;
    private boolean mIsTokenExpired;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_app);

        mNavHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.app_nav_host_fragment);
        mNavController = mNavHostFragment.getNavController();

        mBottomNavigationView = findViewById(R.id.nav_bottom_nav_view);
        NavigationUI.setupWithNavController(mBottomNavigationView, mNavController);

        mAuthorizationViewModel = ViewModelProviders.of(this).get(AuthorizationViewModel.class);

        mAuthorizationViewModel.getLoggedInUserAuth().observe(this, new Observer<Authorization>() { //Observe changes to Authorization LiveData exposed by getLoggedInUserAuth()
            @Override
            public void onChanged(@Nullable Authorization authorization) {
                mBinding.setViewModel(authorization);
                mIsLoggedIn = authorization == null? false: authorization.isLoggedIn();
                mIsTokenExpired = authorization == null ? true : authorization.isTokenExpired();
                if(!mIsLoggedIn || mIsTokenExpired){
                    if (authorization != null){
                        Log.i("CurrentAuth", "tokenExpiresAt?: "+ authorization.getExp());
                    }
                    mNavController.navigate(R.id.start_login); //every time authorization is changed, we check if valid else we react by prompting user to login
                }
            }
        });
    }
}

LogoutFragment.java

public class LogoutFragment extends Fragment {

    private AuthorizationViewModel mAuthorizationViewModel;
    private Authorization mAuth;
    private FragmentLogoutBinding mBinding;

    public LogoutFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mAuthorizationViewModel = ViewModelProviders.of(getActivity()).get(AuthorizationViewModel.class);
        mAuthorizationViewModel.getLoggedInUserAuth().observe(getActivity(), new Observer<Authorization>() {
            @Override
            public void onChanged(Authorization authorization) {
                mAuth = authorization;
            }
        });
        // Inflate the layout for this fragment
        mBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_logout,container,false);
        View view = mBinding.getRoot();
        mBinding.setViewModel(mAuth);
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        new AlertDialog.Builder(getContext())
                .setTitle(R.string.title_logout_fragment)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        mAuthorizationViewModel.logoutCurrentUser(); //toggle login status, this will mutate LiveData by updating the database record then UI will react and call login fragment
                    }
                })
                .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.cancel();
                        Navigation.findNavController(view).popBackStack();
                    }
                })
                .setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialogInterface) {
                    }
                })
                .show();
    }
}

为时已晚,但可能会对某人有所帮助。

我在执行此操作时遇到了同样的问题

MyDao myDao;
private LiveData<List<T>> liveList;
//in constructor of repo after initializing myDao;
    this.liveList = myDao.getAllData();
//somewhere in repo
    for(T t : liveList.getValue()){/*computation*/}

我就是这样解决的

MyDao myDao;
//in constructor of repo don't do this because called on main thread
    this.list = myDao.getAll();
//in constructor of repo initialize your Dao (in this case myDao)
//somewhere in repo  (must not be on main thread)
    for(T t : myDao.getAll()){/*computation*/} //do this on background thread

在 MyDao 中

@Query("SELECT * FROM myTable")
List<T> getAll();
@Query("SELECT * FROM myTable")
LiveData<List<T>> getAllData();

或者,如果您在其他地方(不是存储库)访问 liveList,那么您必须为相同的地方设置一个观察者