2

I was writing an app with the SQLiteOpenHelper class and decided to re-build it all implementing the MVVM pattern using Room for some extra usability. My first issue is that the Room needs to load the database file from the assets folder using the .createFromAsset("database/data_table.db"). It should copy it to the device and then all processes go from there but it doesn't copy it as required, the developer website doesn't specify more than how to call it. Here is the Database class with explanation of each method for learning purposes, since I am in the learning process.

@Database(entities = MarkerObject.class, version = 1, exportSchema = false)
public abstract class MarkerDatabase extends RoomDatabase {

    /**We create the instance so we wouldn't create multiple instances
     of the database and use the same instance throughout the application
     which is accessed through the static variable **/
    private static MarkerDatabase instance;

    /**
     *This is used to access the Dao,
     * no body is provided because Room takes care of that,
     * abstract classes don't have methods but the room generates it because of the @Dao annotation.
     */
    public abstract MarkerDao markerDao();

    /**
     * This method creates a single instance of the database,
     * then we can call this method from the outside so we have a handle to it.
     * Synchronized means that only one thread at a time can access the database
     * eliminating creating multiple instances.
     */
    public static synchronized MarkerDatabase getInstance(Context context){

        /** we want to instantiate the database
         * only if we don't have an instance, thus the if condition */
        if (instance == null) {
            instance = Room.databaseBuilder(context.getApplicationContext(),
                    MarkerDatabase.class, "locations_table")
                    .createFromAsset("database/locations_table.db")
                    .build();
        }
        return instance;
    }
} 

The DAO

@Dao
public interface MarkerDao {

    @Query("SELECT * FROM locations_table")
    LiveData<List<MarkerObject>> getAllMarkers();

}

The Repository

public class MarkerRepository {
  
    private MarkerDao markerDao;
    private LiveData<List<MarkerObject>> allMarkers;

    public MarkerRepository(Application application){
    
    MarkerDatabase markerDatabase = MarkerDatabase.getInstance(application);
    
    markerDao = markerDatabase.markerDao();
  
    allMarkers = markerDao.getAllMarkers();
}
public LiveData<List<MarkerObject>> getAllMarkers(){return allMarkers; }

}

The ViewModel

public class MarkerViewModel extends AndroidViewModel {
  
    private MarkerRepository repository;
    private LiveData<ArrayList<MarkerObject>> allMarkers;

    /**
     * We use the application as context in the constructor
     * because the ViewModel outlives the activities lifecycle.
     * To avoid memory leaks we use application as context instead of activities or views.
     */
    public MarkerViewModel(@NonNull Application application) {
        super(application);
        
        repository = new MarkerRepository(application);
        allMarkers = repository.getAllMarkers();
    }
    public LiveData<ArrayList<MarkerObject>> getAllMarkers(){
        return allMarkers;
    }
}

The MainActivity

public class MainActivity extends AppCompatActivity {
    private MarkerViewModel markerViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.textView);

        markerViewModel = new ViewModelProvider(this).get(MarkerViewModel.class);
        markerViewModel.getAllMarkers().observe(this, new Observer<List<MarkerObject>>() {
            @Override
            public void onChanged(List<MarkerObject> markerObjects) {
                textView.setText(markerObjects.toString());
            }
        });
    }
}

Now I understand that this might be a stupid question to some and I might be judged for it but I don't have other sources other than the internet to learn from. All help is very well appreciated.

Edward Aarma
  • 305
  • 2
  • 8
  • 1
    "but it doesn't copy it as required" -- that would imply that your database already exists. If you ran the app before, then added this line, you will need to clear your app's data or uninstall the app. – CommonsWare Dec 27 '20 at 20:56
  • From the other answer i got that a query must first be executed to create the database, but as i specified there after providing a query, then i arrived at an error in the calling of the ViewModel. But thank you either ways. – Edward Aarma Dec 30 '20 at 15:17

1 Answers1

1

Your code is fine and should work without any problems.

A couple of things to check:

  • First: make sure that location_table.db file exists under app\src\main\assets\database directory

It should copy it to the device and then all processes go from there but it doesn't copy it as required, the developer website doesn't specify more than how to call it

  • Second: createFromAsset() & createFromFile never get executed and create the database file unless you do some transaction or query to the database. Check the accepted answer here for more info.

UPDATE

and the error is java.lang.RuntimeException: Cannot create an instance of class com.example.roomtextfirst.MarkerViewModeL

First I can't see any problems with your new shared code. I just need you to make sure that you have the below dependencies in module gradle file:

implementation 'androidx.fragment:fragment:1.2.2'
implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
implementation 'androidx.lifecycle:lifecycle-service:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'

And not the deprecated ones:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-rc01'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0-rc01'

Also thanks to check the answers here that might guide you as well.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • Now the assets folder was created as required but I did not have the call to any methods from the DAO, as I was not aware that it will only create once querid, now that i have added the call for getting all the markers from the repository to the ViewModel to the MainActivity, then i receive an error on ```markerViewModel = new ViewModelProvider(this).get(MarkerViewModel.class); ``` and the error is ``` java.lang.RuntimeException: Cannot create an instance of class com.example.roomtextfirst.MarkerViewModel ```, so at least a step further. – Edward Aarma Dec 30 '20 at 15:11
  • @EdwardAarma please update the question with the ViewModel class, dao, and if you've a Repository .. so we can have a full picture – Zain Dec 30 '20 at 15:30
  • 1
    @EdwardAarma thanks for sharing.. please check updated answer.. also this error can have several reasons, so you might check the attached link in the answer. – Zain Dec 30 '20 at 21:38
  • Thank you very much for looking into this, i had added most of the dependencies but was missing the lifecycle process, service and savedstate . That solved the database copying issue. Anyways thank you, I was stuck on it for a while and would most likely have taken a while longer. Just one last question if you might know, when I set the MarkerObject to string for the textView, then it returns the lines as [com.example.roomtextfirst.MarkerObject@cf83c81,...more rows], i assume I can't directly convert a List to String but by my knowledge it should be able to be done. – Edward Aarma Dec 31 '20 at 00:34
  • 1
    welcome :) .. `MarkerObject` is your data/pojo class .. in java Object class has `toString()` method.. if you want to convert an object to String, then override the `toString` that can return some field values to be put into TextView or printed to the user as a log – Zain Dec 31 '20 at 00:39