2

Short version: what is the best practice way to access maintain a history for certain messages from both an activity and from a service?

Long version: I have an activity and a service, which both may be running or not. I want to keep a message log (history) in an object an persist it in a file and be able to e.g. delete entries.

When I have such history in the service and one in the activity I run into sync problems. So, any advice, what the best solution would be?

  • ideally I could use the methods from the history class in both the service and activity. Probably not possible.
  • I could write and read the file in each action. Probably not very efficient in the long run.
  • do I really need to setup a service for the history and handle all actions with it via intents?

It is a bit similiar to "proper way to access DB from both Activity and a started Service?", but with just an own class instead of a SQLite DB.

Any advice?


Conclusion: Use a ContentProvider with a SQLite-DB. Short version of the code:

package com.example.history;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;

public class HistoryContentProvider extends ContentProvider {

    static final String PROVIDER_NAME = "com.example.HistoryContentProvider";
    static final String URL = "content://" + PROVIDER_NAME + "/history";
    static final Uri CONTENT_URI = Uri.parse(URL);

    static final String id = "id";
    static final String normalized_number = "normalized_number";
    static final String display_name = "display_name";

    static final int uriCode = 1;
    static final UriMatcher uriMatcher;
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(PROVIDER_NAME, "history", uriCode);
    }

    @Override
    public boolean onCreate() {
        Context context = getContext();
        DatabaseHelper dbHelper = new DatabaseHelper(context);
        db = dbHelper.getWritableDatabase();
        if (db != null) {
            return true;
        }
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(TABLE_NAME);

        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
        case uriCode:
            return "vnd.android.cursor.dir/history";
        default:
            throw new IllegalArgumentException("Unsupported URI: " + uri);
        }   }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long rowID = db.insert(TABLE_NAME, "", values);
        if (rowID > 0) {
            Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
            getContext().getContentResolver().notifyChange(_uri, null);
            return _uri;
        }
        throw new SQLException("Failed to add a record into " + uri);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = 0;
        switch (uriMatcher.match(uri)) {
        case uriCode:
            count = db.delete(TABLE_NAME, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        int count = 0;
        switch (uriMatcher.match(uri)) {
        case uriCode:
            count = db.update(TABLE_NAME, values, selection, selectionArgs);
            getContext().getContentResolver().notifyChange(uri, null);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
        return count;
    }

    private SQLiteDatabase db;
    static final String DATABASE_NAME = "historyDb";
    static final String TABLE_NAME = "history";
    static final int DATABASE_VERSION = 3;
    static final String CREATE_DB_TABLE = " CREATE TABLE " + TABLE_NAME
            + " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
            + normalized_number + " TEXT NOT NULL, "
            + display_name + " TEXT NOT NULL, ";

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(CREATE_DB_TABLE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
            onCreate(db);
        }
    }

}
Community
  • 1
  • 1
Michael Schmidt
  • 1,199
  • 4
  • 14
  • 18

2 Answers2

1

I have an activity and a service, which both may be running or not. I want to keep a message log (history) in an object an persist it in a file and be able to e.g. delete entries.

What you are describing there sounds exactly like a ContentProvider! Link to documentation.

You can use a ContentResolver instance to access data in the ContentProvider from anywhere, be it Activity or Service. The ContentProvider and ContentResolver already handle most of the work for you and basically you just need to implement how you want to save the data in the ContentProvider. The rest is already taken care of! The ContentProvider may have been designed to be used with a SQLiteDatabase - and I would recommend that you use a database - but there is nothing preventing you from saving the data in another way.

Xaver Kapeller
  • 49,491
  • 11
  • 98
  • 86
0

If you are not looking for DB style persistence, then maybe a Queue with File backed persistence is what you are looking for:

This maybe of use

https://github.com/square/tape/blob/master/tape/src/main/java/com/squareup/tape/QueueFile.java

Tip: Create a QueueFile singleton in your App class, and access it from your Activities or services.

Ed Manners
  • 459
  • 4
  • 8