200 lines
5.5 KiB
Markdown
200 lines
5.5 KiB
Markdown
# Room and MVVM
|
||
|
||
## ROOM
|
||
|
||
### Room Persistence Library
|
||
|
||
- An architecture component
|
||
- A database layer on top of SQLite
|
||
- Takes care of tasks that `SQLiteHelper` dealt with
|
||
- Like compile-time validation of SQL queries
|
||
- Allows us to think about objects with fields rather than tables with rows
|
||
|
||
#### Room Components
|
||
|
||

|
||
|
||
###### Database
|
||
|
||
- Contains a database holder
|
||
- A singleton serves as the main access point to persistent data
|
||
|
||
###### Entities
|
||
|
||
- Java classes that represent (or become) tables in the database
|
||
|
||
###### DAO
|
||
|
||
- Data Access Object (software design pattern)
|
||
- An abstract interface for accessing the database to retrieve a given entity
|
||
|
||
#### Database
|
||
|
||
An abstract class that
|
||
|
||
- Is annotated with `@Database`
|
||
- Extends `RoomDatabase`
|
||
- Lists the entities associated with the database
|
||
- Includes abstract methods that return DAO interfaces for the entities
|
||
|
||
Is generally implemented as a **singleton** to retrieve a reference to the database with the application context
|
||
|
||
- Returned by `RoomDatabase.databaseBuilder`
|
||
|
||
A more structured approach to the database lifecycle
|
||
|
||
- `onCreate`, `onOpen`, `onDestructiveMigration`
|
||
- When the database schema is modified, we must define a migration strategy
|
||
|
||
Room **does not** support database access on the main thread
|
||
|
||
- Queries could take a while, so they shouldn’t be done on the main/UI thread
|
||
|
||
#### Entity
|
||
|
||
- Plain old Java class
|
||
- Represents a table in the underlying database
|
||
- Sets of related fields
|
||
- Annotated to describe how to think about it in terms of the database
|
||
- Needs to have a `@PrimaryKey`
|
||
- Field names become column names, unless we annotate an alternate
|
||
- `@ColumnInfo(name = 'my_column_name')`
|
||
- `@Ignore` certain fields
|
||
- Embedded entities, 1-to-1 or 1-to-many relationships
|
||
- `@Embedded`, `@Relation`, `@Transaction`
|
||
|
||
#### DAOs
|
||
|
||
- Interface / Abstract class
|
||
- Common practice to have one DAO per entity
|
||
- Implementation is created at compile time
|
||
|
||
Specify convenience queries, annotated to tell Room what to do
|
||
|
||
- `@Insert` generates an implementation that inserts all parameters into the database in a single transaction.
|
||
- `@Update` updates a set of entities given as parameters using a query that matches against the primary key of each entity.
|
||
- If the entity is updated, it will update the underlying SQL database
|
||
- `@Query` read/write operations as something like a conventional database query.
|
||
- `@Delete` removes entities given as parameters.
|
||
|
||
### MVVM Architecture
|
||
|
||

|
||
|
||
VIEW (UI controller) is the V
|
||
|
||
ViewModel is the current state of the data needed to populate the view
|
||
|
||
Room is the model; it is all the data for the entire application
|
||
|
||
ViewModel $\subset$ (is a subset of) Room Database
|
||
|
||
NOTE: Room is just the database manager, others exist.
|
||
|
||
Repository - as far as the view model is concerned, it asks the repository for all data. It abstracts access to different possible back-ends. The view model doesn’t need to know how the data is stored, e.g. whether data is stored on `data/.cache` or on a web server `ftp://192.168.0.5`.
|
||
|
||
The repository is responsible for connecting to the website and downloading data into cache to support functionality when the internet drops out.
|
||
|
||
**All view models** get their data from the **same repository**
|
||
|
||
# Coding Example
|
||
|
||
```java
|
||
import androidx.room.Entity
|
||
|
||
@Entity(tableName = "myTable")
|
||
public class Record {
|
||
|
||
@PrimaryKey
|
||
@NonNull
|
||
@ColumnInfo(name = "name") // only needed if they don't match
|
||
|
||
public Record(@NonNull String name, String colour) {
|
||
this.name = name;
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
Interface DAO
|
||
|
||
```java
|
||
//Can be an abstract class or interface
|
||
//Room will generate the implementation based on this
|
||
|
||
import androidx.room.Doa;
|
||
|
||
@Dao
|
||
public interface RecordDao {
|
||
@Insert
|
||
void insert (Record r);
|
||
|
||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||
void insertLots (Record... r);
|
||
|
||
@Query("DELETE FROM myTable")
|
||
void deleteALl();
|
||
|
||
@Query("SELECT * FROM myTable ORDER BY name ASC")
|
||
List<Record> getAlphaRecord();
|
||
|
||
@Query("SELECT * FROM myTable")
|
||
LiveData<List<Record>> getAlphaRecord();
|
||
//LiveData is lifecycle aware
|
||
}
|
||
```
|
||
|
||
Boilerplate database stuff
|
||
|
||
```java
|
||
import androidx.room.RoomDatabase;
|
||
|
||
@Database(entities = {Record.class}, version=1, exportScheme=false)
|
||
public abstract class myDatabase extends RoomDatabase {
|
||
|
||
public abstract RecordDao();
|
||
|
||
private static volatile myDatabase INSTANCE;
|
||
|
||
//as we're not allowed to query on the main thread
|
||
static final ExecutorService dbWriter = Executors.newFixedThreadPool(4);
|
||
|
||
//context needed to know file path
|
||
static myDatabase getDatabse(final Context context) {
|
||
|
||
if (INSTANCE == null) {
|
||
synchronised (myDatabase.class) {
|
||
INSTANCE = Room.databaseBuilder(
|
||
context.getApplicationContext(),
|
||
myDatabase.class, "myDatabase")
|
||
.fallbackToDestructiveMigration()
|
||
.build();
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Main Activity
|
||
|
||
```java
|
||
public class MainActivity extends .. {
|
||
|
||
myDatabase db;
|
||
RecordDao rDao;
|
||
|
||
protected onCreate (...){
|
||
db = myDatabase(getApplicationContext());
|
||
rDao = db.recordDao();
|
||
}
|
||
|
||
public void addNewRecord(){
|
||
myDatabase.dbWriter.execute(() -> {
|
||
Record r = new Record ("data", "data");
|
||
rDao.insert(r);
|
||
})
|
||
}
|
||
}
|
||
```
|