Search code examples
javaandroidandroid-roomsynchronousandroid-livedata

Getting Initial Value for LiveData Always Returning Null


I am trying to load the loggedInUser from the Local Room Database, when the App starts. I would like to skip prompting user to log-in if the saved Authentication Token of the previously saved user is still valid!

So, from the DAO, I want to return a LiveData object containing the previously logged-in user, then observe it for subsequent changes. The challenge I have is that the method to get the currently logged-in user always returns null if I wrap the result inside a LiveData, but it returns the expected user if returned as a POJO.

How can I force LiveData to run synchronously just to initialize the value and then thereafter listen to subsequent changes? I really want to combine the two behaviors as the authentication may be invalidated by a background syncing task or when the user logs out(these actions will either replace or update the saved token and I would like to be reactive to such updates with the help of LiveData).

Here is what I have tried so far:

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

As you can see, I am calling mAuthorizationViewModel.init() so I can load or initialize the loggedInUserAuth from the local database, and then observe the same LiveData instance with mAuthorizationViewModel.getLoggedInUserAuth().observe() on the next line! But the value returned for loggedInUserAuth is always null!

Kindly help, thanks!


Solution

  • Create a getter method of mAuthorization in class AuthorizationRepository

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

    Then modify your AuthorizationViewModel class like below

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