Pages

Friday, 1 December 2017

Android Room Database Tutorial with Fragments, RecyclerView, LiveData, ViewModel and Data Binding


Intro

This tutorial will implement an Android App with Room Database, Fragments, RecyclerView, LiveData, ViewModel and Data Binding. Lets stop messing around, we'll get to the code.


Step 0 - The App's Build.Gradle file

We need to add the following to the app/build.gradle file for the app to work. We're using Java 1.8 for the Lambda in the Fragment and Data Binding.

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  dataBinding {
    enabled = true
  }
}

dependencies {
  ...
  implementation "android.arch.lifecycle:extensions:1.0.0"
  implementation "android.arch.persistence.room:runtime:1.0.0"
  annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
  testImplementation "android.arch.persistence.room:testing:1.0.0"
}


Step 1 - Database Entity, the Model

Start with the Data Driven Design, we'll first implement the model.

@Entity
public class BlogPost {

    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "title")
    private String title;

    /**
     * Default Constructor
     *
     * Room Database will use this no-arg constructor by default.
     * The others are annotated with @Ignore,
     * so Room will not give a warning about "Multiple Good Constructors".
     */
    public BlogPost() {
    }

    @Ignore
    public BlogPost(String title) {
        this.title = title;
    }

    // Setters and Getters...
}


Step 2 - DAO - Data Access Object

We'll need to outline an interface class that will allow us to access the Database's content via queries.

@Dao
public interface BlogPostDao {

    @Query("SELECT * FROM blogpost")
    LiveData<List<BlogPost>> getAllBlogPosts();

    @Query("SELECT * FROM blogpost WHERE id = :id LIMIT 1")
    LiveData<List<BlogPost>> findBlogPostById(long id);

    @Query("SELECT * FROM blogpost WHERE title LIKE :title LIMIT 1")
    LiveData<List<BlogPost>> findBlodPostByTitle(String title);

    @Query("SELECT COUNT(*) FROM blogpost")
    int rowCount();

    @Insert
    void insertBlogPosts(BlogPost... blogPosts);

    @Update
    void updateBlogPosts(BlogPost... blogPosts);

    @Delete
    void deleteBlogPosts(BlogPost... blogPosts);
}


Step 3 - Room Database Implementation

To access the generated DAO class, the RoomDatabase needs to be implemented.

@Database(entities = {BlogPost.class}, version = 1, exportSchema = false)
public abstract class BlogPostDatabase extends RoomDatabase {

    private static BlogPostDatabase INSTANCE;

    public static BlogPostDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = Room.databaseBuilder(
                    context.getApplicationContext(),
                    BlogPostDatabase.class,
                    "BlogPostsDatabase")
                    .build();
        }

        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }

    public abstract BlogPostDao blogPostDao();
}


Step 4 - View Model Implementation

The AndroidViewModel show the LiveData List that the MainActivity and MainFragment will observe.

public class BlogPostsViewModel extends AndroidViewModel {

    private final LiveData<List<BlogPost>> blogPosts;

    public BlogPostsViewModel(@NonNull Application application) {
        super(application);

        blogPosts = BlogPostDatabase
                      .getInstance(getApplication())
                      .blogPostDao(
                      .getAllBlogPosts();
    }

    public LiveData<List<BlogPost>> getBlogPosts() {
        return blogPosts;
    }
}


Step 5 - RecyclerView Adapter

To populate the RecyclerView, we'll need the Adapter.

public class MainActivityFragmentRecyclerViewAdapter extends
         RecyclerView.Adapter
          <MainActivityFragmentRecyclerViewAdapter
                .MainActivityFragmentRecyclerViewHolder> {

    private List<BlogPost> blogPosts;

    public MainActivityFragmentRecyclerViewAdapter(List<BlogPost> blogPosts) {
        this.blogPosts = blogPosts;
    }

    @Override
    public MainActivityFragmentRecyclerViewHolder onCreateViewHolder(
              ViewGroup parent, int viewType) {
        RecyclerItemBinding itemBinding = RecyclerItemBinding.inflate(
                LayoutInflater.from(parent.getContext()), parent, false);

        return new MainActivityFragmentRecyclerViewHolder(itemBinding);
    }

    @Override
    public void onBindViewHolder(
             MainActivityFragmentRecyclerViewHolder holder, int position) {
        String blogPostTitle = blogPosts.get(position).getTitle();
        holder.bind(blogPostTitle);
    }

    @Override
    public int getItemCount() {
        return blogPosts.size();
    }

    public void setBlogPosts(List<BlogPost> blogPosts) {
        this.blogPosts = blogPosts;
        notifyDataSetChanged();
    }

    static class MainActivityFragmentRecyclerViewHolder
               extends RecyclerView.ViewHolder {

        RecyclerItemBinding binding;

        MainActivityFragmentRecyclerViewHolder(RecyclerItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        void bind(String blogPostTitle) {
            binding.blogPostTextView.setText(blogPostTitle);
            binding.executePendingBindings();
        }
    }
}


Step 6 - Observing the ViewModel in the Fragment

We observe the ViewModel and use it to update the RecyclerView's content.

public class MainActivityFragment extends Fragment {

    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(
                            LayoutInflater inflater,
                            ViewGroup container,
                            Bundle savedInstanceState) {

        FragmentMainBinding binding =
              DataBindingUtil.inflate(
                      inflater, R.layout.fragment_main, container, false);

        MainActivityFragmentRecyclerViewAdapter recyclerViewAdapter =
             new MainActivityFragmentRecyclerViewAdapter(new ArrayList<>());

        binding.recyclerView.setLayoutManager(
                        new LinearLayoutManager(getActivity()));

        binding.recyclerView.setAdapter(recyclerViewAdapter);

        BlogPostsViewModel viewModel =
              ViewModelProviders.of(this).get(BlogPostsViewModel.class);

        viewModel.getBlogPosts().observe(
            MainActivityFragment.this, recyclerViewAdapter::setBlogPosts);

        return binding.getRoot();
    }
}


Step 7 - The Other Fragments and Activities

There's not too much craziness in the other Fragments and Activities. We're using DataBinding and there's an Activity-Fragment pair for adding a new Database Entry.

The Activity Package on GitHub: AndroidRoomDatabaseTutorialBasic / activity.


Step 8 - The Layout Files

There's no magic in the layout files. They only have the layout tag for the Data Binding to work. So I'll leave the link to the directory from the Git Hub.

The Layout Directory on GitHub: AndroidRoomDatabaseTutorialBasic / main / res / layout.


Step 9 - Optionally add a Database Intialiser

We can use the following class to populate the database with hard coded data.

public class DatabaseInitializer {

    public static void populateAsync(final BlogPostDatabase database) {
        new PopulateDbAsync(database).execute();
    }

    private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {

        private final BlogPostDatabase database;

        PopulateDbAsync(BlogPostDatabase database) {
            this.database = database;
        }

        @Override
        protected Void doInBackground(final Void... params) {
            // If the Database is empty, add the initial data.
            if (database.blogPostDao().rowCount() == 0) {
                List<BlogPost> blogPosts = new ArrayList<>();
                blogPosts.add(new BlogPost("Blog Post #1"));
                blogPosts.add(new BlogPost("Blog Post #2"));
                blogPosts.add(new BlogPost("Blog Post #3"));

                database.blogPostDao()
                        .insertBlogPosts(
                            blogPosts.toArray(new BlogPost[blogPosts.size()]));
            }

            return null;
        }
    }
}


Examining The Database

Using DB Browser for SQL Lite, we can attach it to the database file to view the database. The database file can be got in Android Studio's Device File Explorer view.

The database file is in the directory: data/data/com.package/database/



No comments:

Post a Comment

Note: only a member of this blog may post a comment.