Coder Social home page Coder Social logo

Comments (4)

Zhuinden avatar Zhuinden commented on July 23, 2024

I've uploaded a copy of the backup of the codelab instructions, which are a better documentation for JOIN-based Room queries than anything in "Room with a view".

https://www.mediafire.com/file/3yoc76u1xsqrlee/Android_Persistence.zip/file

1. Introduction

Architecture components are a set of Android libraries that help you structure your app in a way that is robust, testable, and maintainable.

Caution: The architecture component libraries are in an alpha state of development. The API may change before general availability, and you may encounter stability or performance issues.

The new Room Persistence Library guarantee SQL statements at compile-time and removes boilerplate code related to storing data locally in SQLite using the following features:

At compile time, Room validates each query against the schema, so that broken SQL queries result in compile time errors, instead of runtime failures.
Room abstracts away some of the underlying implementation details related to working with raw SQL.
You can use Room to observe changes to data stored in a database, and expose these changes as LiveData objects.
Room also defines thread constraints which you can use to help address common issues that can negatively impact the performance of apps, such as accessing persistent storage from the main thread.
What you'll build
In this codelab, you begin with a sample app and add code through a series of steps, integrating the various persistence components as you progress.

What you'll need
Android Studio 2.3 or greater
To be familiar with the LiveData and ViewModel lifecycle components. You can work through a separate codelab that covers these topics.


2. Setup Your Environment
In this step, you download the code for the entire codelab and then run a simple example app.

Click the following button to download all the code for this codelab.

Unzip the code, and then open the project using Android Studio version 2.3 or newer.
Run the Step 1 run configuration to make sure there are no errors or missing dependencies.


3. Introducing Room

If your app handles non-trivial amounts of data, you can gain a number of benefits by storing at least some of that data locally using Room. The most common benefit is to optimize network connectivity, by caching relevant data to ensure users can still browse that content while they are offline. Any changes the user makes to content can later be synced to the server, once the device is back online.

The core Android framework provides built-in support for working with raw SQL data. While the built-in APIs are very powerful, they also present a number of development challenges:

They are relatively low-level, and require a large amount of development time and effort.
Raw SQL queries are not verified at compile-time.
You must manually update SQL queries to reflect changes in your data graph. This process is unnecessarily time consuming, and error-prone.
You write and maintain a lot of boilerplate code to convert between SQL queries and data objects.
Room is designed to abstract away the underlying database tables and queries, and encourage best-practice development patterns on Android.

By default, Room doesn't allow you to issue database queries on the main thread to avoid poor UI performance. However querying on the main thread is enabled in this codelab for simplicity.

Introducing the sample app

In the remainder of this codelab, you work on a library app that keeps track of book loans to users. The code creates an in-memory database with three tables: books, users, and loans, as well as some sample data.

Take a few minutes to explore the package db to learn about the entities, data access objects, database, and sample data you use later in the codelab. Open and inspect the following three files:

The User data class.
The Book data class.
The Loan data class.
Notice that the @entity annotation to mark a data class as a persistable entity. At least one of the class fields must be annotated with the @PrimaryKey annotation.

Next, open and inspect the following 3 files:

The UserDao interface.
The BookDao interface.
The LoanDao interface.

The @dao annotation is used to create data access objects, which define SQLite queries. You explore data access objects in more detail later in this codelab.

Finally, review the AppDatabase class, which is annotated using @database. The AppDatabase class establishes a logical grouping between the UserDao, BookDao, and LoanDao data access object interfaces. The AppDatabase class also defines the required version number, which is used to track and implement database migrations.


4. Step 1 - Fetching data with DAO

A Data Access Object (DAO) is an abstract class or interface that includes methods to define database queries. The annotated methods in this class are used to generate the corresponding SQL at compile time. This abstraction helps to reduce the amount of repetitive boilerplate code you need to maintain. Unlike runtime SQL, these annotated methods are parsed and validated at compile time. For example, the following interface method defines a SQL query that takes a String with a username and returns a list of books loaned to that user:

 @Query("SELECT * FROM Book " +
   "INNER JOIN Loan ON Loan.book_id == Book.id " +
   "WHERE Loan.user_id == :userId "
  )
  public List<Book> findBooksBorrowedByUser(String userId);

The following method illustrates another example, and it's used to insert books into the database. The method replaces the existing record if a conflict occurs:

   @Insert(onConflict = REPLACE)
    void insertBook(Book book);

Open the UserDao class and review some examples of the different types of queries you can define in a DAO:

@query
@insert
@delete
@update

Now try to define a new query, or modify an existing one, to introduce invalid SQL and rebuild the project. You should observe an error similar to the following message:

Error:(77, 23) error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: userId)

Next, open the step1 package and review the UsersActivity class, which fetches data from the database using the following code:

 private void fetchData() {
     StringBuilder sb = new StringBuilder();
     List<User> youngUsers = mDb.userModel().loadAllUsers();
     for (User youngUser : youngUsers) {
         sb.append(String.format("%s, %s (%d)\n",
             youngUser.lastName, youngUser.name, youngUser.age));
     }
     mYoungUsersTextView.setText(sb);
 }

Caution: For simplicity, this activity has a reference to the database but in a real app the database would belong in a different component.

Run the app, and notice there's now an impostor in the list:

Create a new @query in the UserDao class which retrieves the list of users below a specified age:

 @Query("SELECT * FROM User WHERE age < :age")
 List<User> findYoungerThan(int age);

Next, call the new method from the activity, to only fetch young users:

  List<User> youngUsers = mDb.userModel().findYoungerThan(35);

Now run the app again and notice that the query retrieves the expected results:

Caution: The operation you added in this step runs on the main thread. However input and output operations must be executed in the background. At this stage, you are probably not noticing any performance problems because the queries are simple and the database is stored in memory, rather than on disk. Also, placing all this logic in the activity is not considered best practice. You fix these problems in future steps.


**5. Step 2 - Define Entity Relationships

There are a number of relationships that you can use to define links between entities:

1-to-1: describes a relationship between 2 entities, where the first is exclusively linked to the second. For example, a book may only have 1 ISBN, and an ISBN may only refer to a single book.
1-to-many: describes a relationship where 1 entity is linked to 1 or more entities of a different kind. For example, a library may own 1 or more copies of a book, but each book may only be owned by 1 library.
Many-to-many: a relationship where 1 or more entities of 1 kind can be linked to 1 or more entities of another kind. For example, a book may have 1 or more authors, and each author may write 1 or more books.
In this step, you use an entity to define a many-to-many relationship called Loan to record when a user borrows and returns a book. You update the app to show a list of books that were borrowed by user Mike.

Review the following annotated query from db.BookDao.java:

     @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);

Next, run the app and review the list of books borrowed by user Mike.

You might notice there's a performance problem; the UI is blocked while the database is being populated. This operation is purposely slow, to simulate a worst-case scenario.

The database is currently populated synchronously, so begin by removing the following code in JankShowUserActivity.java:

  DatabaseInitializer.populateSync(mDb);

Replace the code you removed with the following statement, to switch to using an asynchronous task to populate the data:

  DatabaseInitializer.populateAsync(mDb);

Now run the app and review the list of books borrowed by user Mike as you did earlier in this step. You should find that no books are listed:

Take a moment to see if you can identify the cause of this behaviour.

The database is being populated in the background, but you're still querying it in the main thread. The issue is caused by a race condition. Query results arrive before the Loan table is populated. If you press REFRESH after a couple of seconds, the results appear:

There are two problems with this approach:

The query is still being executed on the main thread, potentially blocking the UI.
There is no way to know when the database tables are finished being populated.
One way to solve the issue you observed might be to add a callback or listener to delay loading data until the database is populated. However, adding too many callbacks can complicate development and debugging. A more elegant solution would be to subscribe to, and observe changes in the database. You do this in the next step using lifecycle-aware components.


6. Step 3 - Observe LiveData from a ViewModel

In this step, you integrate the code you added earlier in the codelab with LiveData, a lifecycle-aware component which can be observed, typically described as an observable. Simply wrapping your @query return type with LiveData provides you with database observers for minimal extra effort. You also move the reference to the database, from the activity, to a ViewModel.

Navigate to db.BookDao.java and create a method that does the same thing as findBooksBorrowedByNameSync() but wraps the return type in a LiveData object.
Navigate to and review, step3.BooksBorrowedByUserViewModel. The BooksBorrowedByUserViewModel class exposes the list of books wrapped in a LiveData object, and is also responsible for creating and populating the database:
class BooksBorrowedByUserViewModel extends ViewModel {
public final LiveData<List> books;
}

Instead of retrieving the list of books from the database, retrieve the LiveData object from the DAO and use it to subscribe to changes using the name field. Update step3.BooksBorrowedByUserViewModel.java to include the following code in the constructor:

    // books is a LiveData object so updates are observed.
    books = mDb.bookModel().findBooksBorrowedByName("Mike");

Navigate to step3.BooksBorrowedByUserActivity and review how the ViewModel is provided in onCreate:

    mViewModel = ViewModelProviders.of(this).get(BooksBorrowedByUserViewModel.class);

Next, complete the subscribeUiBooks method by subscribing the activity to changes in the ViewModel object's list of books,:

  private void subscribeUiBooks() {
      mViewModel.books.observe(this, new Observer<List<Book>>() {
          @Override
          public void onChanged(@NonNull final List<Book> books) {
              showBooksInUi(books, mBooksTextView);
          }
      });
  }

The UI will now update smoothly whenever a loan record is added.

You can find the solution to this step in the step3_solution package.

from android-persistence.

zawawimanja avatar zawawimanja commented on July 23, 2024

wow I am surprised that they are not responding.

from android-persistence.

Zhuinden avatar Zhuinden commented on July 23, 2024

Nobody is responsible for these codelabs anymore, I guess.

If that's the case though, they should have also set it read-only.

Nonetheless, thankfully the codelabs were available on the web archive.

from android-persistence.

DarkOhms avatar DarkOhms commented on July 23, 2024

I'm wondering how to apply the different approach to the RoomWordSample.

from android-persistence.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.