0% found this document useful (0 votes)
16 views49 pages

L9 - Data Storage 2

The document discusses mobile application data storage using SQLite databases and Room persistence library. It provides an overview of storing app data locally in a SQLite database for offline access. It describes the primary components of Room - the Database class, Data Access Objects (DAO) and Data Entities. Data Entities represent tables and are annotated classes, DAOs provide database access methods, and the Database class provides DAO instances.

Uploaded by

YAN XIN LIM
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views49 pages

L9 - Data Storage 2

The document discusses mobile application data storage using SQLite databases and Room persistence library. It provides an overview of storing app data locally in a SQLite database for offline access. It describes the primary components of Room - the Database class, Data Access Objects (DAO) and Data Entities. Data Entities represent tables and are annotated classes, DAOs provide database access methods, and the Database class provides DAO instances.

Uploaded by

YAN XIN LIM
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

WIA2007

MOBILE APPLICATION DEVELOPMENT


APP DATA STORAGE II
Created and Updated by S. Ong
Adapted by Chiew TK
Semester 1, 2023/2024

[email protected]
2

OVERVIEW
SAVE DATA (STORAGE)
• Few options to save your
mobile app data:
• App-specific storage
• Shared storage
• Preferences
• Database  Part 2
• Overview
• SQLite
• Data Entities
• Data Access Objects
• Object Relationship

[email protected]
3

OVERVIEW
DATABASE – RECALL FROM PART1
• Structured data stored in a private database.
• After the Jetpack library – Room Persistence Library is
introduced to handle all database access.
• Room is a persistence library that's part of Android
Jetpack.
• Room is an abstraction layer on top of a SQLite database.
• Do not need explicit permission to access the data.
• Other applications CANNOT access the data.
• The data will be removed when the mobile app is
uninstalled.

[email protected]
4

LOCAL DATABASE
• It’s always important to store data locally – that the user
can access it (and still able to perform some crucial
operations) while the application is offline.
• SQLite is automatically created when the mobile
application is installed on the mobile device.

[email protected]
5

THE SQLITE DATABASE


OVERVIEW
• Open source and lightweight relational database
administration system.
• Included in Android framework.
• Take a minimum amount of disk space. Just a single
disk file in the database:
• No server is required.
• It is an application library itself.
• Doesn’t need installation.
• Cross-platform
• can be copied and used on a different machine with a
different architecture.

[email protected]
6

THE SQLITE DATABASE


THE ISSUES
• In the past (before Jetpack library), we interact directly
with SQLite databases using android.database.sqlite
API package (please refer to
https://developer.android.com/reference/android/dat
abase/sqlite/package-summary to know more about
it).
• However, using these APIs causes the following issues:
Tedious to manually handle the operations because
there are multiple classes involved.
No compile time verification for raw SQL query – might
be prone to error.
Use of many boilerplate codes (repetitive codes with
no/little variance to accomplish minor functionalities) to
convert between SQL queries and data objects.

[email protected]
7

ROOM PERSISTENCE LIBRARY


• Hence, room persistence library is introduced to:
✓ Compile-time SQL query verification
✓ Ready and convenient annotation to reduce the
boilerplate codes (also more organized, and with simpler
operation).
✓ Streamline the database migration path.
• It is also an API BUT provide an abstraction layer on top
of SQLite for managing the data as Java object – and
can leverage the full power of SQLite database.
• Not an ORM (Object Relational Mapping) that contains
more restrictions when perform operation directly using
SQLite.

[email protected]
8

SETUP ROOM
• Add dependencies in build.gradle file:

Add dependencies

[email protected]
9

PRIMARY COMPONENTS
• There are THREE major
components involved in
Room:
• Database class
• Hold the database and
provide access to the
persisted data.
• Data Access Objects (DAO)
• Provide methods to
perform operations in the
database.
• Data Entities
• Represents table in the
database.
Room Library Architecture

[email protected]
10

PRIMARY COMPONENTS
• The database class provides your app with instances of the
DAOs associated with that database.
• In turn, the app can use the DAOs to retrieve data from the
database as instances of the associated data entity objects.
• The app can also use the defined data entities to update rows
from the corresponding tables, or to create new rows for
insertion.

[email protected]
11

DATA ENTITIES
• Use Room to define the database schema without
writing any SQL code.
• Use data entities to represent the object to store.
• Each entity = table in the associated Room database.
• Each Room entity is a class annotated as @entity
• Each instance = row of data in the table.

[email protected]
12

DATA ENTITIES
• To define the data entity:
@entity annotation
@Entity
public class User {
@PrimaryKey Table name
public int id;
Field names (and data type)
public String firstName;
public String lastName;
} Primary Key using @PrimaryKey
annotation (MUST define one)

[email protected]
13

DATA ENTITIES
• To define the data entity (customization):

@Entity(tableName = "users") if the table name and class


public class User { name are different
@PrimaryKey
public int id;

@ColumnInfo(name = "first_name")
public String firstName; if the field names and
column names are different
@ColumnInfo(name = "last_name")
public String lastName;
}

[email protected]
14

DATA ENTITIES
• To define the data entity (customization):

@Entity(tableName = "users") if the table name and class


public class User { names are different.
@PrimaryKey (autoGenerate = true)
public int id; if want the primary key to be
auto-generated.
@ColumnInfo(name = "first_name")
public String firstName; if the field names and
column names are different.
@ColumnInfo(name = "last_name")
public String lastName;
}

[email protected]
15

DATA ENTITIES
• To define the data entity (customization):

@Entity(primaryKeys = {"firstName", "lastName"})


public class User {
public String firstName;
public String lastName;
} If contains composite key

[email protected]
16

DATA ENTITIES
• To define the data entity (ignore field):

@Entity
public class User {
@PrimaryKey
public int id;

public String firstName; If do not want the field of the


public String lastName; entity to persist
- Use @ignore annotation
@Ignore
Bitmap picture;
}

[email protected]
17

DATA ENTITIES
• To define the data entity (ignore field):

@Entity(ignoredColumns = "picture") If do not want the field of


public class RemoteUser extends User the entity to persist (and
{ the field is inherited from
@PrimaryKey parent entity
public int id; - Use ignoredColumns
parameter
public boolean hasVpn;
}

[email protected]
18

DATA ACCESS OBJECT (DAO)


• Need to use Data Access Object (DAO) to interact with
the stored data that uses the Room database.
• Each DAO consists of methods that offer abstract
access to the database.
• Room Persistence Library will generate the actual
implementation during compile time.
• Have to define each DAO as an either interface or
abstract class.
• Annotate using @Dao
• Do not have property, but contains the definition of one
or more methods to interact with data in a database.

[email protected]
19

DATA ACCESS OBJECTS


• To define DAO:
@Dao
public interface UserDao { @Dao annotation
@Query("SELECT * FROM user")
List<User> getAll();
Query method to writing
@Query("SELECT * FROM user WHERE uid IN (:userIds)") your own SQL query to
List<User> loadAllByIds(int[] userIds); interact with database

@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +


"last_name LIKE :last LIMIT 1")
User findByName(String first, String last);

@Insert Convenience method to


void insertAll(User... users); insert, update and delete
without writing SQL codes
@Delete
void delete(User user);
}
[email protected]
20

DATA ACCESS OBJECTS


CONVENIENCE METHODS - INSERT
• Used to define simpler insertion of the data in the
database.
• @Insert annotation
• When an insert method is called, Room inserts each
passed entity instance into the corresponding table.
• If a single parameter is received:
• Returns a long value – rowid created
• If the parameter is an array/collection:
• Returns array/collection of long values – rowids created

[email protected]
21

DATA ACCESS OBJECTS


CONVENIENCE METHODS - INSERT
• Example of insertion method

@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);

@Insert
public void insertBothUsers(User user1, User user2);

@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}

[email protected]
22

DATA ACCESS OBJECTS


CONVENIENCE METHODS - UPDATE
• A simpler method to define a specific row update in
the Room database table.
• Use @Update annotation.
• Accepts data entity instances as the parameter.
• Uses primary key to match with the passed instances:
• If no match, no changes are made.
• Can optionally change the return type to int to obtain
the number of rows updated.

[email protected]
23

DATA ACCESS OBJECTS


CONVENIENCE METHODS - UPDATE
• Example of update method

@Dao
public interface UserDao {
@Update To update one or more users
public void updateUsers(User... users);
}

[email protected]
24

DATA ACCESS OBJECTS


CONVENIENCE METHODS - DELETE
• A simpler method to define the method to delete
specific rows in the table.
• Use @Delete annotation.
• Same with insert and delete methods, it accepts data
entity instances as the parameter.
• Uses primary key to match with the passed instances:
• If no match, no deletions are made.
• Can optionally change the return type to int to obtain
the number of rows deleted successfully.

[email protected]
25

DATA ACCESS OBJECTS


CONVENIENCE METHODS - DELETE
• Example of delete method

@Dao
public interface UserDao {
@Delete To delete one or more users
public void deleteUsers(User... users);
}

[email protected]
26

DATA ACCESS OBJECTS


QUERY METHOD
• All convenience methods are simpler methods
• Perform simple operations using the primary key
• The @Query allows us to write our own SQL queries (
more complex) to perform customized insertion,
update, and deletion in the table.
• Room validates SQL queries during compile-time that
significantly reduces run-time errors during any
database operations.
• For example
The Select query returns all
@Query("SELECT * FROM user")
public User[] loadAllUsers(); users’ data in the table.

[email protected]
27

DATA ACCESS OBJECTS


QUERY METHOD – OBTAIN TABLE SUBSET
• Sometimes, not all columns in the table are relevant.
• Should always maintain a good habit of only retrieving
relevant data whenever necessary – to save resources.
• To obtain a subset of the table (say we only need first
name and last name in the user table):

public class NameTuple {


@ColumnInfo(name = "first_name")
public String firstName;

@ColumnInfo(name = "last_name") Let’s create an object to


@NonNull hold these two values
public String lastName;
}

[email protected]
28

DATA ACCESS OBJECTS


QUERY METHOD – OBTAIN TABLE SUBSET
• To obtain a subset of the table (say we only need first
name and last name in the user table):
@Query("SELECT first_name, last_name FROM user") Then customized the
public List<NameTuple> loadFullName(); query method to only
obtain the two values.
• Room allows returning a simple object from any of the
queries if it matches the set of result columns on the
returned object.
• Room understands that the query method will return the
two values and these values can be mapped to
NameTuple class.
• If cannot be mapped (different column data type/number
of columns) – display warning.

[email protected]
29

DATA ACCESS OBJECTS


QUERY METHOD – PASS PARAMETER
• Passing parameters to the query is important because it
helps with the filtering/provides a more specific target.
• Room uses the method parameter as the bind
parameter in the query.
• For example:

@Query("SELECT * FROM user WHERE age > :minAge")


public User[] loadAllUsersOlderThan(int minAge);
Returns all users above minAge.

Use “:” in the query to indicate it is


a bind parameter.

[email protected]
30

DATA ACCESS OBJECTS


QUERY METHOD – PASS PARAMETER
• For example (multiple parameters):
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND
:maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

Pass two parameters

@Query("SELECT * FROM user WHERE first_name LIKE :search " +


"OR last_name LIKE :search")
public List<User> findUserWithName(String search);

Same method parameter but


is used twice in the query

[email protected]
31

DATA ACCESS OBJECTS


QUERY METHOD – PASS PARAMETER
• For example (collection of parameter – unknown
number):
@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

Returns data for a


subset of regions.

[email protected]
32

DATA ACCESS OBJECTS


QUERY METHOD – MULTIPLE TABLES
• Use the JOIN clause to combine multiple tables in
fulfilling the query.
• For example (joins three tables together to return the
books that are currently on loan to a specific user):

@Query("SELECT * FROM book " +


"INNER JOIN loan ON loan.book_id = book.id " +
"INNER JOIN user ON user.id = loan.user_id " +
"WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);

[email protected]
33

DATA ACCESS OBJECTS


QUERY METHOD – MULTIPLE TABLES
• For example (joins two tables together to return the
different columns from different table):
@Dao
public interface UserBookDao {
@Query("SELECT user.name AS userName, book.name AS bookName " +
"FROM user, book " +
"WHERE user.id = book.user_id")
public LiveData<List<UserBook>> loadUserAndBookNames();

// You can also define this class in a separate file, as long as you add the
// "public" access modifier.
static class UserBook {
Reference for LiveData:
public String userName;
public String bookName; https://developer.android.com/topic/
} libraries/architecture/livedata
}

[email protected]
34

DATA ACCESS OBJECTS


QUERY METHOD – OTHERS
• Multi Map
• https://developer.android.com/training/data-
storage/room/accessing-data#multimap
• Special Type Return (Paging Library and Cursor access)
• https://developer.android.com/topic/libraries/architectu
re/paging
• https://developer.android.com/training/data-
storage/room/accessing-data#special-return-types
• https://developer.android.com/reference/kotlin/android
/database/Cursor

[email protected]
35

OBJECT RELATIONSHIP
• SQLite is a structured and relational database.
• A relationship can be defined between entities:
• Embedded object
• One-to-One relationship
• One-to-Many relationship
• Many-to-Many relationship
• And others.

[email protected]
36

OBJECT RELATIONSHIP
EMBEDDED OBJECT
• Group relevant (and cohesive) fields as a whole object.
• Use @Embedded annotation to represent the object that
is decomposed into its subfield within the table.
public class Address {
public String street;
public String state;
public String city;

@ColumnInfo(name = "post_code") public int postCode;


}

@Entity User has an Address


public class User { object that decomposed
@PrimaryKey public int id;
to street, state, city, and
public String firstName; postcode.
@Embedded public Address address;
}
[email protected]
37

OBJECT RELATIONSHIP
EMBEDDED OBJECT
• Group relevant (and cohesive) fields as a whole object.
• Use @Embedded annotation to represent the object that
is decomposed into its subfield within the table.
public class Address {
public String street;
public String state;
public String city;

@ColumnInfo(name = "post_code") public int postCode;


}

@Entity This User table contains id,


public class User { firstName, street, state,
@PrimaryKey public int id;
city, and postcode
public String firstName; columns.
@Embedded public Address address;
}
[email protected]
38

OBJECT RELATIONSHIP
1-TO-1 RELATIONSHIP
• Each instance of the parent entity corresponds to
exactly one instance of the child entity.
• For example:
• A music streaming app where a user has a library of
songs.
• One user can only have one library, while a library can
only correspond to one user.

[email protected]
39

OBJECT RELATIONSHIP
1-TO-1 RELATIONSHIP
• Example (music streaming app)

@Entity Create two classes:


public class User { User and Library
@PrimaryKey public long userId;
public String name;
public int age;
}

@Entity
public class Library { One of the entities must
@PrimaryKey public long libraryId; include one variable that
public long userOwnerId; references to the primary
} key of another entity.

[email protected]
40

OBJECT RELATIONSHIP
1-TO-1 RELATIONSHIP
• Before query, model the 1-to-1 relationship between
these two entities:
public class UserAndLibrary { Create a new data class
@Embedded public User user; that has two instances
@Relation( (one from parent entity
parentColumn = "userId", and one from child entity)
entityColumn = "userOwnerId"
) Use @Relation annotation
public Library library; to the instance of the child
} entity, and set:
parentColumn – primary
key in parent entity
entityColumn – column
name in child entity that
references to
[email protected]
parentColumn
41

OBJECT RELATIONSHIP
1-TO-1 RELATIONSHIP
• Lastly, to perform the query (add Query method to
DAO class):
Returns all instances that
pairs and parent-child
@Transaction entities.
@Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();

Use @Transaction
annotation because two
queries will be performed
- To ensure the queries are
performed atomically

[email protected]
42

OBJECT RELATIONSHIP
1-TO-MANY RELATIONSHIP
• Each instance in the parent entity corresponds to zero
to many instances of the child entity
• BUT each child can only correspond to exactly one
instance in the parent entity.
• For example (music streaming app)
• One user can create zero to many playlists, but each
playlist belongs to only one user.
• Create two classes – User and Playlist
@Entity @Entity
public class User { public class Playlist {
@PrimaryKey public long userId; @PrimaryKey public long playlistId;
public String name; public long userCreatorId;
public int age; public String playlistName;
} }

[email protected]
43

OBJECT RELATIONSHIP
1-TO-MANY RELATIONSHIP
• Before query, model the 1-to-many relationship
between these two entities:
public class UserWithPlaylists { Codes are similar with 1-to-
@Embedded public User user; 1 relationship.
@Relation(
parentColumn = "userId",
entityColumn = "userCreatorId"
)
public List<Playlist> playlists; The only difference is the
} list of child instances.

• To query:
@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylists> getUsersWithPlaylists();

[email protected]
44

OBJECT RELATIONSHIP
MANY-TO-MANY RELATIONSHIP
• Each instance in the parent entity corresponds to zero
to many instances of the child entity
• AND each child instance corresponds to zero-to-many
instances in the parent entity.
• For example (music streaming app)
• Each playlist can include many songs.
• Each song can be in many playlists.

[email protected]
45

OBJECT RELATIONSHIP
MANY-TO-MANY RELATIONSHIP
• Many-to-many relationship: @Entity
public class Playlist {
@PrimaryKey public long playlistId;
No reference to the parent public String playlistName;
entity in the child entity. }

Create a third class (cross- @Entity


reference table) to represent public class Song {
an associate entity. @PrimaryKey public long songId;
public String songName;
public String artist;
Must have columns for the }
primary key from both
classes. @Entity(primaryKeys = {"playlistId",
"songId"})
public class PlaylistSongCrossRef {
public long playlistId;
public long songId;
}
[email protected]
46

OBJECT RELATIONSHIP
MANY-TO-MANY RELATIONSHIP
• Next step?
• If you want to query many playlists with a list of songs for
each playlist:
• Create a new data class that contains a
single Playlist object and a list of all Song objects that the
playlist includes.
• If you want to query many songs with a list of playlists for
each song:
• Create a new data class that contains a single Song object
and a list of all playlist objects that the song includes.

[email protected]
47

OBJECT RELATIONSHIP
MANY-TO-MANY RELATIONSHIP
public class PlaylistWithSongs {
@Embedded public Playlist playlist;
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = @Junction(PlaylistSongCrossref.class)
)
public List<Song> songs;
} Use associateBy property
in @Relation to model the
public class SongWithPlaylists { relationship.
@Embedded public Song song;
@Relation(
parentColumn = "songId",
entityColumn = "playlistId",
associateBy = @Junction(PlaylistSongCrossref.class)
)
public List<Playlist> playlists;
}
[email protected]
48

OBJECT RELATIONSHIP
MANY-TO-MANY RELATIONSHIP
• To query, on DAO class:
Returns all the resulting
@Transaction PlaylistWithSongs objects.
@Query("SELECT * FROM Playlist")
public List<PlaylistWithSongs> getPlaylistsWithSongs();

@Transaction
@Query("SELECT * FROM Song")
public List<SongWithPlaylists> getSongsWithPlaylists();

Returns all the resulting


SongWithPlaylists objects.

[email protected]
49

MORE REFERENCES
• Asynchronous Query Execution (running database
query on the main thread):
• https://developer.android.com/training/data-
storage/room/async-queries
• Test and debug database
• https://developer.android.com/training/data-
storage/room/testing-db

[email protected]

You might also like