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.