3

I have an application that allows a user to enter their credentials into a Fragment which then inserts or updates a record in a Room database. The transaction is handled by a CredentialsRepository.

On the networking side, I have a DataRepository that works with a NetworkServiceController to create calls to my remote API. In order to access the remote API I have an interceptor that adds the user credentials to each call.

What I need is to load those credentials when the application loads, and whenever the data in the Room database changes. I can do this using LiveData in my login screen (a View), but how can I observe the user credential data in my DataRepository using LiveData?

How do I register my DataRepository class as an Observer? Right now my app crashes...I am pretty sure the way I am registering the observer is wrong.

Here is my CredentialsRepository:

    import androidx.lifecycle.LiveData;
    import ca.thirdgear.homeautomation.HomeAutomationApp;
    import ca.thirdgear.homeautomation.dao.UserCredentialsDAO;
    import ca.thirdgear.homeautomation.entity.UserCredentials;
    import javax.inject.Inject;
    import javax.inject.Singleton;

    /**
     * Repository Class for data from the Room Database
     */
    @Singleton
    public class CredentialsRepository
    {
        @Inject
        UserCredentialsDAO credentialsDAO;

        @Inject
        public CredentialsRepository()
        {
            HomeAutomationApp.getAppComponent().inject(this);
        }

        public void setCredentials(String userEmail, String password)
        {
            //create UserCredentials object with the credentials provided from the UI.
            UserCredentials newOrUpdatedUserCredentials = new UserCredentials(userEmail, password);

            //insert user credentials into the database
            Runnable myRunnableObject = new SetUserCredentials(credentialsDAO, newOrUpdatedUserCredentials);
            new Thread(myRunnableObject).start();
        }

        public LiveData<UserCredentials> getCredentials()
        {
            return credentialsDAO.getUser();
        }
    }

Here is my DataRepositoryClass

    /**
     * Repository class for handling network calls to REST API
     */
    @Singleton
    public class DataRepository
    {
        //Network components
        @Inject
        NetworkServiceController networkServiceController;

        @Inject
        UserCredentialsDAO credentialsDAO;

        @Inject
        CredentialsRepository credRepo;

        private HomeAutomationAPI homeAutomationAPIService;
        private MutableLiveData<String> zoneOneState;
        private MutableLiveData<String> zoneTwoState;
        private MutableLiveData<String> garageDoorState;
        private MutableLiveData<String> adtSystemState;
        private MutableLiveData<String> panelTemperature;
        private String username;
        private String password;


        @Inject
        public DataRepository()
        {
            //Inject Network Backend Components & Room DAO
            HomeAutomationApp.getAppComponent().inject(this);

            //create credentials observer
            final Observer<UserCredentials> credentialsObserver = new Observer<UserCredentials>() {
                @Override
                public void onChanged(UserCredentials userCredentials)
                {
                    //update string values for credentials
                    username = userCredentials.getUsername();
                    password = userCredentials.getPassword();

                    //call createNetworkService() to create a new network service object
                    createNetworkService();
                }
            };

            //register the observer
            //this does not work...app crashes
            credRepo.getCredentials().observeForever(credentialsObserver);

            zoneOneState = new MutableLiveData<>();
            zoneTwoState = new MutableLiveData<>();
            garageDoorState = new MutableLiveData<>();
            adtSystemState = new MutableLiveData<>();
            panelTemperature = new MutableLiveData<>();

            //Poll the server every 5 seconds on a separate thread for changes in the IO
            Thread thread = new Thread(new Runnable(){
                public void run()
                {
                    // Moves the current Thread into the background
                    android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);

                    while(true)
                    {
                        try {
                            sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        getIoStatus();
                    }
                }
            });
            thread.start();
        }

        /*
             Create the network service when there are changes in the user credentials data.
         */
          private void createNetworkService()
          {
              homeAutomationAPIService = networkServiceController.createService(HomeAutomationAPI.class, username, password);
          }


        ... deleted methods not relevant ...

    }

EDIT: Observer registration can occur in DataRepository like this:

credRepo.getCredentials().observeForever(credentialsObserver);

or like this:

credentialsDAO.getUser().observeForever(credentialsObserver);

However, I need to create the network service in the constructor by calling:

createNetworkService();

Otherwise the application still crashes.

My application now works with the addition of the line of code above, but a new problem has been created. When the application is launched in a new process, it takes several seconds before any network data is available. My guess is that DataRepository creation takes place faster than data can be retrieved from the database, and my network services is initially created with null or unknown user credentials. Is this a correct assumption?

mike
  • 137
  • 2
  • 16
  • dont think you can observe from your repo, but you can instead return those live data objects to your view or your view model and observe from an activity or fragment – a_local_nobody Jul 12 '19 at 18:58
  • have your repository return those live data objects to the activity or fragment and observe from there – a_local_nobody Jul 12 '19 at 18:58
  • Don't know if fits you, but why don't you fetch the current credentials at each network call? This way you don't have to observe their changes. – Thalis Vilela Jul 12 '19 at 19:02
  • I thought about that, but I am concerned about the time it takes to do that on every call. I would rather cache the credentials in local fields, and only update them when they change in Room. – mike Jul 12 '19 at 19:06
  • Also fetching the credentials on every API call creates a new network service.... leading to a lot of objects being created. I did try this suggestion, but the app crashes. – mike Jul 12 '19 at 19:18
  • Could you please provide crashlog? Also since Repository class is not associated with a `Lifecycle` the only way to observe is using `observeForever` but that is almost always not needed. – Arka Prava Basu Jul 15 '19 at 18:59
  • I unfortunately don't have logging set up right now, but can. I won't likely have time to get this complete until later this week... completing priorities. – mike Jul 16 '19 at 00:04

1 Answers1

0

The issue was that I did not have Room set up to allow main thread queries using:

allowMainThreadQueries()

Usage:

public static CredentialsDatabase getDatabase(final Context context)
{
    if (INSTANCE == null)
    {
        synchronized (CredentialsDatabase.class)
        {
            if (INSTANCE == null)
            {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        CredentialsDatabase.class, "credentials_database")
                        .addCallback(initializeCallback)
                        //allow main thread queries only used for DataRepository instantiation as
                        //credentials are required to set up network services.
                        .allowMainThreadQueries()
                        .build();
            }
        }
    }
    return INSTANCE;
}

I need the value of the UserCredentials persisted in memory when DataRepository is created so that I can immediately make network calls when my views are active. I also created a query in my DAO that returned UserCredentials not wrapped in a LiveData object. For information regarding how I came to this solution, refer to this Stackoverflow post.

In future I will also create a log file to easy troubleshooting.

mike
  • 137
  • 2
  • 16