0

I have implemented the MVVM architecture in my app. I have my activity, viewmodel, repository, DAO, and database classes. The database holds objects that contain different lists that I want to switch between and have my RecyclerView show the currently selected list. My understanding is that row IDs in SQLite start at 1, but the insert method in my repository (which calls the @Insert method in my DAO) always starts with a row ID of 0. For testing purposes, I'm using LiveData to get all the objects in the database and when I log their IDs, they properly begin with 1 and go all the way to n. I don't want to maintain a list of all the objects in the database in memory, only the single object that contains the currently selected list. When a user creates a new list (or selects an existing list), I want to have my RecyclerView display its contents and observe any changes to that list. I can't start observing an object without its proper corresponding ID.

How do I propagate the proper ID to MainActivity? If my ViewModel and Activity code are needed please tell me and I will edit the post.

When I log the ID from the Repository

enter image description here

When I log the LiveData in MainActivity the proper IDs show

enter image description here

My Database class

@TypeConverters({ArrayListConverter.class}) // List converter for database
@Database(entities = ListContainer.class, version = 1, exportSchema = false)
public abstract class ListContainerDatabase extends RoomDatabase {

  private static ListContainerDatabase dbInstance;
  private static final String DB_NAME = "list_container_db";
  private static final int NUM_THREADS = 4;

  final static ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);

  public abstract ListContainerDao getDao();

  public static synchronized ListContainerDatabase getInstance(Context context) {
    if(dbInstance == null) {
      dbInstance = Room.databaseBuilder(
              context.getApplicationContext(),
              ListContainerDatabase.class, DB_NAME)
              .fallbackToDestructiveMigration()
              .build();
    }
    return dbInstance;
  }

  public static Executor getExecutor() {
    return executor;
}

My DAO

@Dao
public interface ListContainerDao {

  @Insert
  long insertListContainer(ListContainer container);

... // other database queries

}

My Repository

public class Repository {

  private static final String TAG = "Repository";

  private ListContainerDao listContainerDao;
  private long id;

  private Executor executor = ListContainerDatabase.getExecutor();

  public Repository(Application application) {
    ListContainerDatabase containerDb = ListContainerDatabase.getInstance(application);
    listContainerDao = containerDb.getDao();
  }

  public long insertListContainer(ListContainer container) {
    executor.execute(new Runnable() {
      @Override
      public void run() {
        id = listContainerDao.insertListContainer(container);  // this returns ID starting at 0
      }
    });
    Log.i(TAG, "insertListContainer: from Repository id is :" + id);
    return id;
  }
}
gig6
  • 307
  • 5
  • 16

1 Answers1

1

You should consider the asynchronity of your code here:

private long id;

public long insertListContainer(ListContainer container) {
    // Timestamp #1. This started, first time id is not initialised, equals to 0
    executor.execute(new Runnable() { 
    @Override
      public void run() {
        id = listContainerDao.insertListContainer(container);  
        // Timestamp #3. This returns you id = 1, but with some delay, so this value changed id and will be returned only next time you call the method 
      }
    });
    // Timestamp #2. Main thread isn't blocked, so it doesn't wait for runnable to be executed as well and returns first time id = 0, next time - changed value - 1, and so on
    Log.i(TAG, "insertListContainer: from Repository id is :" + id); 
    return id;
  }

If you want to get result of asynchronous operation in Java you can use:

sergiy tikhonov
  • 4,961
  • 1
  • 10
  • 27
  • The callback approach in the link is pretty clear. However, in that example, class A directly instantiates class B, passing itself as a listener to B's constructor. In my case, my MainActivity instantiates the ViewModel and the ViewModel instantiates the Repository (which is where the callback would reside). My question is, should I pass MainActivity as a listener to the ViewModel and when the ViewModel instantiates the Repository pass it again to the Repository's constructor? Does this have a potential for a memory leak? – gig6 Nov 08 '20 at 22:50
  • 1
    "Does this have a potential for a memory leak?" - Yes, it does. "Should I pass MainActivity as a listener to the ViewModel" - No, you shouldn't, ViewModel shouldn't hold the reference to activity, there is a recommended way of communication in MVVM - LiveData. "And when the ViewModel instantiates the Repository pass it again to the Repository's constructor" - something like that, but to use ViewModel as a listener. Honestly, I can't say I'm a big fan of callback approach, but in Java it's still an option if you don't want to deal with RxJava. I prefer coroutines in Kotlin ) – sergiy tikhonov Nov 08 '20 at 23:06
  • Thanks for your suggestions. I will explore RxJava as an option. As I mentioned in my original post that I am using LiveData with other methods. Do you think converting my ----- public long insertListContainer(param) to ------- public LiveData insertListContainer(param) would solve my problem? Sorry for the noob questions but I am very new to working with LiveData. – gig6 Nov 08 '20 at 23:36
  • 1
    What I've meant mentioning LiveData - LiveData (or another Observer pattern) is a recommended way when you want to get data from ViewModel to Activity (instead of using straight reference to Activity inside ViewModel). So using callback strategy - ViewModel listens to Repository through callback, after getting value updates LiveData's value, Activity observes it. – sergiy tikhonov Nov 09 '20 at 00:45
  • I ended up using Java's built in Future class. The get() method stops code execution and waits for a result, which is exactly what I was looking for. Your answers prompted me to research and ultimately find the solution. Much thanks. – gig6 Nov 10 '20 at 00:21
  • Yes, Future.get() waits for result but it blocks the thread in which it is being called. If it's a main thread then it's not good. So f you've chosen that way consider CompletebleFuture instead. But it's a Java8 feature and you should make [these steps](https://developer.android.com/studio/write/java8-support) to use it in Android. – sergiy tikhonov Nov 10 '20 at 06:23