获取始终返回Null的LiveData的初始值

时间:2019-02-24 18:26:54

标签: java android android-room synchronous android-livedata

我正在尝试在应用启动时从本地会议室数据库加载 loggedInUser 。如果以前保存的用户保存的身份验证令牌仍然有效,我想跳过提示用户登录的信息!

因此,我想从 DAO 返回一个 LiveData 对象,该对象包含以前登录的用户,然后观察用于后续更改。我面临的挑战是,如果将结果包装在 LiveData 中,则用于获取当前登录用户的方法总是返回null ,但是如果返回的话,它将返回预期的用户作为 POJO

如何强制 LiveData 同步运行 ,仅初始化 ,然后听后续更改?我真的想将这两种行为结合起来,因为身份验证可能会通过后台同步任务用户注销无效(这些操作将替换或更新已保存的令牌,并且我想通过 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 本地数据库,然后在下一行上使用 mAuthorizationViewModel.getLoggedInUserAuth()。observe() 观察相同的 LiveData 实例!但是为 loggedInUserAuth 返回的值总是

请帮助,谢谢!

3 个答案:

答案 0 :(得分:0)

在类zip中创建DriverManager的吸气方法

mAuthorization

然后按如下所示修改您的AuthorizationRepository

public MutableLiveData<Authorization> getAuthorizationResult() {
   return mAuthorization;
}

答案 1 :(得分:0)

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

  1. DAO方法应返回 LiveData
  2. 存储库类中,创建一个 LiveData私有成员变量,而不是 MutableLiveData (这是因为我们将通过更新来突变数据库记录/插入)。成员变量将包含对 DAO方法
  3. 返回的 LiveData 对象的引用
  4. 存储库的构造器中,将 LiveData 对象初始化 DAO方法返回的结果。这样,每次活动开始时,都会加载当前保存的记录
  5. 存储库类中,创建一个 getter ,它将把 LiveData 对象公开给 ViewModel
  6. ViewModel类中,创建一个将 LiveData 对象公开给 View Controller(活动或片段)
  7. 的方法>
  8. 活动或片段中,简单地收听或订阅以查看由 Accessor方法公开的 LiveData 上的更改。 >由 ViewModel
  9. 提供
  10. DAO 还可以公开更新 LiveData 的方法,从而允许 Repository 通过 ViewModel 启用 Activity或Fragment 将更新发送到 LiveData ,同时使所有侦听器保持 active

这是此方案的工作代码:

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

答案 2 :(得分:0)

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

我在做这件事时遇到了同样的问题

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

在我的道

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

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