Room persistence library and Content provider
Asked Answered
D

5

70

Last Couple of days I have been spending times on learning new Android Architecture Components . After following up some blog posts, documentation & tutorials , every components were getting clear to me . But Suddenly I realised what about our old friend Content Provider . I might sound silly , because before writing this question I have spent quite a time searching , Am I be the only one came up with this question . I hadn't got any helpful solution . Anyways here is it , if I want to build up an app with local DB , I will now obviously choose new Architecture Components (live data , view model , room ) without any farther thinking this will be very helpful to make app 10x robust . But If I want my DB datas accessible to other app , for instance To Widget How do I integrate Content Provider with Room ?

Danielladanielle answered 18/10, 2017 at 7:23 Comment(2)
Hi we can connect room DB with Content Provider go Search Sharing Room Database using Kotlin you get my post explaining sameScarlett
medium.com/@aniket93shetty/…Scarlett
C
46

I had the same question by the way. And I found a sample here which answers my question. Hope it does the same with you.

In short, this is in the DAO object which would be called from Content Provider's query() method.

/**
 * Select all cheeses.
 *
 * @return A {@link Cursor} of all the cheeses in the table.
 */
@Query("SELECT * FROM " + Cheese.TABLE_NAME)
Cursor selectAll();

Notice how it returns Cursor object. Other operations, you can see for yourself in more detail in the sample.

This here is choice number 3 in the answer by @CommonsWare, I think.

Corissa answered 23/10, 2017 at 7:8 Comment(5)
thanks a bunch mate . I got confused with the last answer . I had no idea what is matrix cursor . after searching for it I told myself , ok leave it . now your answer and the github link you gave; would be a helpful suggestion . I always love to see samples .Danielladanielle
You can also get a List of Entities directly from DAO avoiding Content Providers. You would simply wrap the all to DAO in a AsyncTask and you're good to go.Retention
This only work if you are providing data to other apps, but it doesn't help you when you want to consume data from other apps. This is a major hole in AAC.Giusto
The sample that @Corissa mentions is the real gem to the solution. It's a great example of how to use a content provider and Room as the back-end. I took it a tiny bit further and access Room via the repository pattern; so that only the repository is accessing the Room database. Happy to post examples if someone needs, but this answer is sufficient.Bootery
Thank for reference to the samples. Is there sense of using the dao injection into the content provider with dagger?Busra
O
14

if I want to build up an app with local DB , I will now obviously choose new Architecture Components (live data , view model , room )

I would not use the term "obviously" there. The Architecture Components are an option, but not a requirement.

But If I want my DB datas accessible to other app , for instance To Widget How do I integrate Content Provider with Room ?

An app widget is unrelated to a ContentProvider. IMHO very few apps should be exposing databases to third parties via ContentProvider, and no apps should be using a ContentProvider purely for internal purposes.

That being said, you have a few choices:

  1. Do not use Room, at least for the tables to be exposed via the ContentProvider

  2. Use Room for internal purposes, but then use classic SQLite programming techniques for the ContentProvider, by calling getOpenHelper() on your RoomDatabase

  3. Use Room in the ContentProvider, writing your own code to build up a MatrixCursor from the Room entities that you retrieve (for query()) or creating the entities for use with other operations (for insert(), update(), delete(), etc.)

Oringas answered 18/10, 2017 at 11:50 Comment(19)
It's acceptable to use a ContentProvider for internal purposes; especially if you need to work with CursorAdapters. Even then, you can and should use ContentProviders "because they provide a nice abstraction" Source at your discretion.Madonnamadora
@Josh: Google often fails to update its documentation. I cannot think of any current Android app development expert who advocates the use of ContentProvider for purely internal use.Oringas
Thanks for your reply. What would you recommend, then, for cases where a Cursor is needed?Madonnamadora
@Josh: I do not know of any case where a Cursor is needed. For example, you cited CursorAdapter. Not only are there other ListAdapter implementations, but RecyclerView is a better view choice in many cases, and RecyclerView does not use CursorAdapter.Oringas
Thank you! I will look into it. The documentation has gotten me confused as it is outdated as you pointed out. I am learning about LiveData<E> and Observables and MVVM as we speak so I'm glad to be rid of ContentProvider if I don't need it.Madonnamadora
@Oringas How would you query data from/for the widget? Doesn't RemoteViewsFactory run in a separate process (host's)? Isn't ContentProvider needed there to avoid multiple RoomDatabases?Midyear
@ooi: "How would you query data from/for the widget?" -- have the AppWidgetProvider delegate the work to a service, for simple app widgets. "Doesn't RemoteViewsFactory run in a separate process (host's)?" -- not by default, as everything is in one process by default. Your problem in that case is threading, and I don't know what the "state of the art" is for RemoteViewsFactory and threading models.Oringas
I use ContentProviders in all of my database driven apps, even for internal use only. The main reason to do so is for its thread-safe implementation. Sure, it adds a bit of overhead from a development standpoint, but the benefits far outweigh that IMO. It's also just as easy to extend the RecyclerView.Adapter class with CursorAdapter functionality. Once you've done that, I think using a ContentProvider is easier than accessing a local database directly, especially without the thread-safe aspects.Sty
@Baron: "The main reason to do so is for its thread-safe implementation" -- SQLiteDatabase is thread-safe, outside of deadlock scenarios. Even for those, a repository would offer a lighter-weight approach than would a ContentProvider. You are welcome to do what you want, but bear in mind that conventional wisdom has moved on from your techniques, and so new approaches (e.g., Room) may not necessarily be conducive to your approach.Oringas
I agree, that ContentProviders are a heavier solution, but I find them easier to work with despite the push to Room. Room also requires additional libraries and classes to convert the table data into POJO constructs, where a ContentProvider does not. Room will certainly add more weight, perhaps even more than a ContentProvider, so I'm hesitant to make the switch just yet. Can you comment on any performance advantages of Room over a ContentProvider?Sty
@Baron: "Room will certainly add more weight" -- in APK size, yes. At runtime, perhaps. "Can you comment on any performance advantages of Room over a ContentProvider?" -- no, as I have not attempted any benchmarks.Oringas
@Oringas Google has updated it's docs on when to use ContentProvider. Here is the link. Though I've used Room for Database abstraction, I'd to use stub ContentProvider to implement SyncAdapter. Also CursorLoaders are dependent on ContentProvider. Hence I would request you to update your answer which says 'no app should use ContentProvider' for internal purpose, as it is misleading. Google IO scheduler app has implemented for internal purpose linkPolydactyl
@ManishMulimani: The iosched app has exported its provider, meaning that it is for external purposes, not internal ones. SyncAdapter is for external purposes, not internal ones (and, few people use SyncAdapter). The documentation that you link to has "content providers are primarily intended to be used by other applications". Hence, I stand by my answer as written.Oringas
@Oringas first, thank you for all your suggestions. I am working on my first Android app, and in my case I need to set up a local Room database, in order to have access to data while offline. Whenever the data connection comes back ON, I need to sync with a remote Server (thru a REST API - using retrofit). As mentioned before, all documentation I have seen suggest to use a SyncAdapter because it will take care of starting the sync process when data is back ON. What is your proposed solution, if not to use SyncAdapter?Bonnard
@Johnny: "all documentation I have seen suggest to use a SyncAdapter" -- that is difficult to believe, since there is almost no documentation on SyncAdapter. "What is your proposed solution, if not to use SyncAdapter? " -- server push (FCM), JobScheduler, AlarmManager, and manual request (e.g., pull-to-refresh).Oringas
I don't agree with the statement no apps should be using ContentProvider internally. you pretty much need one for SyncManager to run.Giusto
@Oringas the sun service handles a whole lot of stuff for you, and works with FCM. There are some very good reasons to use the sync service, which you can look up.Giusto
@BrillPappin: SyncAdapter is not especially popular, and I do not recommend it.Oringas
It's not popular, because most people don't understand how to use it. To me it's a measure of the difference between a junior, and intermediate in terms of experience. It's really not too tough, once you get the hang of it, and it handel's so much for you, that it's well worth the time. That's why most of the primary apps on your phone, use the SyncManager.Giusto
Y
8

Room Library does not have any particular support for Content Provider. You can only write Content Provider on your own and then use Room to query a database.

If you want to use Android Architecture Components and you want to work with SQLite based Content Providers, consider using Kripton Persistence Library: it allows to generate Live Data from DB queries, generate Content Provider for you, and much more. Least but not last: why do you have to write the entire SQL, when you need only to write the where conditions?

Just to be clear, I'm the author of Kripton Persistence Library. I wrote it because I didn't find a unique library that fit all my need in terms of persistence management (and yes, because I like to program).

I wrote an converted version of Google Content Provider Sample with Kripton. You can found it here.

Just to simplify the reading. With Kripton, you only need to define a DAO interface. Content provider will be generated by the annotations. The same DAO converted in Kripton will be:

@BindContentProviderPath(path = "cheese")
@BindDao(Cheese.class)
public interface CheeseDao {

    @BindSqlSelect(fields="count(*)")
    int count();

    @BindContentProviderEntry
    @BindSqlInsert
    long insert(String name);

    @BindContentProviderEntry()
    @BindSqlSelect
    List<Cheese> selectAll();

    @BindContentProviderEntry(path = "${id}")
    @BindSqlSelect(where ="id=${id}")
    Cheese selectById(long id);

    @BindContentProviderEntry(path = "${id}")
    @BindSqlDelete(where ="id=${id}")
    int deleteById(long id);

    @BindContentProviderEntry(path = "${cheese.id}")
    @BindSqlUpdate(where="id=${cheese.id}")
    int update(Cheese cheese);

}

The generated Content Provider exposes DAO's method with URIs. For clearification, I put here only the generated JavaDoc (always by Kripton).

enter image description here

More information about Kripton on its wiki, my site and on my articles .

Yetty answered 10/6, 2018 at 21:50 Comment(0)
H
7

Late post but I bumped in the same issue recently. Finally ended up in using the same Room Database instance for both local and content provider purpose.

enter image description here

So the app itself uses Room Database as usual and Content Provider "wraps" Room Database with "open helper" as follows:

class DatabaseProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        return true
    }

    override fun query(uri: Uri?, projection: Array<out String?>?, selection: String?, selectionArgs: Array<out String?>?, sortOrder: String?): Cursor? {
        val db = roomDatabase.openHelper.readableDatabase
        db.query(...)
    }

    override fun insert(uri: Uri?, values: ContentValues?): Uri? {
        val db = roomDatabase.openHelper.writableDatabase
        db.insert(...)
    }

    override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String?>?): Int {
        val db = roomDatabase.openHelper.writableDatabase
        db.update(...)
    }

    override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String?>?): Int {
        val db = roomDatabase.openHelper.writableDatabase
        db.delete(...)
    }

    override fun getType(uri: Uri?): String? {
    }
}
Hessney answered 28/11, 2018 at 10:26 Comment(0)
A
3

you'd better use the SupportOpenHelper

public class MyContentProvider extends ContentProvider {
    public MyContentProvider() {
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    UserDatabase database;

    @Override
    public boolean onCreate() {
        database = Room.databaseBuilder(getContext(), UserDatabase.class, "user.db").allowMainThreadQueries().build();
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         return database.query(SupportSQLiteQueryBuilder.builder("user").selection(selection, selectionArgs).columns(projection).orderBy(sortOrder).create());
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return database.getOpenHelper().getWritableDatabase().update("user", 0, values, selection, selectionArgs);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return database.getOpenHelper().getWritableDatabase().delete("user", selection, selectionArgs);
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long retId = database.getOpenHelper().getWritableDatabase().insert("user", 0, values);
        return ContentUris.withAppendedId(uri, retId);
    }
}
Against answered 15/1, 2021 at 3:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.