Kripton ORM: how to generate content provider – example 2

Abubusoft Blog

Kripton ORM: how to generate content provider – example 2

Kripton ORM has a lot of features and simplifies a lot the developer life. If your application needs to expose its database with a content provider, in most case you have to write it.

I have already written on this feature, so if you didn’t read it, I suggest you read this post too.

Today I take a Room Library example that I found on GitHub named PersistenceContentProviderSample and I rewrite using Kripton. The example is quite simple: there is an application that uses an SQLite database with a table named cheeses and exposes database by a content provider. The developer needed to write:

  • Cheese class definition
  • Cheese DAO interface
  • SampleDatabase
  • SampleContentProvider

Write the content provider is, for me, a very boring task and good bug generator.

Kripton to the same things, without the need to write the content provider. The content provider is generated by Kripton and generation is driven with specific annotations.

To original source code, please have a look directly at the GitHub repository. For simplicity, I write here only the equivalent source code adapted for Kripton. I put “revisited” source code in the repo kripton-examples/PersistenceContentProviderSample.

@BindTable(name = "cheeses")
public class Cheese {
 
/** The unique ID of the cheese. */
public long id;
 
/** The name of the cheese. */
public String name;
}

The DAO definition:

/**
* Data access object for Cheese.
*/
@BindContentProviderPath(path = "cheese")
@BindDao(Cheese.class)
public interface CheeseDao {
 
/**
* Counts the number of cheeses in the table.
*
* @return The number of cheeses.
*/
@BindSqlSelect(fields="count(*)")
int count();
 
/**
* Inserts a cheese into the table.
*
* @param cheese A new cheese.
* @return The row ID of the newly inserted cheese.
*/
@BindContentProviderEntry
@BindSqlInsert
long insert(Cheese cheese);
 
/**
* Select all cheeses.
*
* @return A {@link Cursor} of all the cheeses in the table.
*/
@BindContentProviderEntry
@BindSqlSelect
List<cheese> selectAll();</cheese>
 
/**
* Select a cheese by the ID.
*
* @param id The row ID.
* @return A {@link Cursor} of the selected cheese.
*/
@BindContentProviderEntry(path = "${id}")
@BindSqlSelect(where ="id=${id}")
Cheese selectById(long id);
 
/**
* Delete a cheese by the ID.
*
* @param id The row ID.
* @return A number of cheeses deleted. This should always be {@code 1}.
*/
@BindContentProviderEntry(path = "${id}")
@BindSqlDelete(where ="id=${id}")
int deleteById(long id);
 
/**
* Update the cheese. The cheese is identified by the row ID.
*
* @param cheese The cheese to update.
* @return A number of cheeses updated. This should always be {@code 1}.
*/
@BindContentProviderEntry(path = "${cheese.id}")
@BindSqlUpdate(where="id=${cheese.id}")
int update(Cheese cheese);
}

Methods of DAO interface marked with @BindContentProviderEntry, will be used to generate a method that will be used in the content provider. The data source definition:

@BindContentProvider(authority = "com.abubusoft.contentprovidersample.provider")
@BindDataSource(daoSet = {CheeseDao.class}, fileName = "sample.db", version = 1, populator = SamplePopulator.class)
public interface SampleDataSource {
 
}

And at last, the populator task that contains data that populate database first time it is created. The original example has a similar mechanism, but it is included in data source definition.

public class SamplePopulator implements SQLitePopulator {
 
  /** Dummy data. */
  static final String[] CHEESES = {
    "Abbaye de Belloc"
  };
 
  @Override
  public void execute() {
    BindSampleDataSource dataSource = BindSampleDataSource.instance();
 
    dataSource.execute((BindSampleDaoFactory daoFactory) -> {
    CheeseDaoImpl dao = daoFactory.getCheeseDao();
 
    for (String item:CHEESES) {
      Cheese bean=new Cheese();
      bean.name=item;
      dao.insert(bean);
    }
 
    return TransactionResult.COMMIT;
    });
  }
}

The generation of the content provider of the data source is pilot by annotation @BindContentProvider that allows defining the authority of content provider, used in the manifest file. The DAO interface used in content provider is marked with @BindContentProviderPath(path = “cheese”). Each method used in content provider is marked with @BindContentProviderEntry. The generated content provider source code is, Javadoc included:

/**
* <p>This is the content provider generated for {@link SampleDataSource}</p>
*
* <h2>Content provider authority:</h2>
* <pre>com.abubusoft.contentprovidersample.provider</pre>
*
* <h2>Supported query operations</h2>
* <table>
* <tr><th>URI</th><th>DAO.METHOD</th></tr>
* <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></td><td>{@link CheeseDaoImpl#selectAll1}</td></tr>
* <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre></td><td>{@link CheeseDaoImpl#selectById2}</td></tr>
* </table>
*
* <h2>Supported insert operations</h2>
* <table>
* <tr><th>URI</th><th>DAO.METHOD</th></tr>
* <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></td><td>{@link CheeseDaoImpl#insert0}</td></tr>
* </table>
*
* <h2>Supported update operations</h2>
* <table>
* <tr><th>URI</th><th>DAO.METHOD</th></tr>
* <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${cheese.id}</pre></td><td>{@link CheeseDaoImpl#update4}</td></tr>
* </table>
*
* <h2>Supported delete operations</h2>
* <table>
* <tr><th>URI</th><th>DAO.METHOD</th></tr>
* <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre></td><td>{@link CheeseDaoImpl#deleteById3}</td></tr>
* </table>
*
*/
public class BindSampleContentProvider extends ContentProvider {
  /**
   * <p>content provider's URI.</p>
   * <pre>content://com.abubusoft.contentprovidersample.provider</pre>
   */
  public static final String URI = "content://com.abubusoft.contentprovidersample.provider";
 
  /**
   * <p>datasource singleton</p>
   */
  private static BindSampleDataSource dataSource;
 
  /**
   * <p>Content provider authority</p>
   */
  public static final String AUTHORITY = "com.abubusoft.contentprovidersample.provider";
 
  /**
   * <p>URI matcher</p>
   */
  private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
  /**
   * <p>Uri</p>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre>
   */
  private static final Uri URI_PATH_CHEESE_1 = Uri.parse(URI+"/cheese");
 
  /**
   * <p>Uri</p>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/#</pre>
   */
  private static final Uri URI_PATH_CHEESE_2 = Uri.parse(URI+"/cheese/#");
 
  static final String PATH_CHEESE_1 = "cheese";
 
  static final String PATH_CHEESE_2 = "cheese/#";
 
  static final int PATH_CHEESE_1_INDEX = 1;
 
  static final int PATH_CHEESE_2_INDEX = 2;
 
  /**
   * <h2>URI standard</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></p>
   * <h2>URI with parameters</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre>
   *
   * <p>Method associated to this URI is {@link CheeseDaoImpl#insert0}</p>
   */
  public static final Uri URI_CHEESE_INSERT = URI_PATH_CHEESE_1;
 
  /**
   * <h2>URI standard</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></p>
   * <h2>URI with parameters</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre>
   *
   * <p>Method associated to this URI is {@link CheeseDaoImpl#selectAll1}</p>
   */
  public static final Uri URI_CHEESE_SELECT_ALL = URI_PATH_CHEESE_1;
 
  /**
   * <h2>URI standard</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/#</pre></p>
   * <h2>URI with parameters</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre>
   *
   * <p>Method associated to this URI is {@link CheeseDaoImpl#deleteById3}</p>
   */
  public static final Uri URI_CHEESE_DELETE_BY_ID = URI_PATH_CHEESE_2;
 
  /**
   * <h2>URI standard</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/#</pre></p>
   * <h2>URI with parameters</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre>
   *
   * <p>Method associated to this URI is {@link CheeseDaoImpl#selectById2}</p>
   */
  public static final Uri URI_CHEESE_SELECT_BY_ID = URI_PATH_CHEESE_2;
 
  /**
   * <h2>URI standard</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/#</pre></p>
   * <h2>URI with parameters</h2>
   * <pre>content://com.abubusoft.contentprovidersample.provider/cheese/${cheese.id}</pre>
   *
   * <p>Method associated to this URI is {@link CheeseDaoImpl#update4}</p>
   */
  public static final Uri URI_CHEESE_UPDATE = URI_PATH_CHEESE_2;
 
  static {
    sURIMatcher.addURI(AUTHORITY, PATH_CHEESE_1, PATH_CHEESE_1_INDEX);
    sURIMatcher.addURI(AUTHORITY, PATH_CHEESE_2, PATH_CHEESE_2_INDEX);
  }
 
  /**
   * <p>Create datasource and open database in read mode.</p>
   *
   * @see android.content.ContentProvider#onCreate()
   */
  @Override
  public boolean onCreate() {
    if (KriptonLibrary.context()==null) {
      KriptonLibrary.init(getContext());
    }
    dataSource = BindSampleDataSource.instance();
    dataSource.openWritableDatabase();
    return true;
  }
 
  /**
   * <p>Close database.</p>
   *
   * @see android.content.ContentProvider#shutdown()
   */
  @Override
  public void shutdown() {
    super.shutdown();
    dataSource.close();
  }
 
  /**
   *
   * <h2>Supported query operations</h2>
   * <table>
   * <tr><th>URI</th><th>DAO.METHOD</th></tr>
   * <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></td><td>{@link CheeseDaoImpl#selectAll1}</td></tr>
   * <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre></td><td>{@link CheeseDaoImpl#selectById2}</td></tr>
   * </table>
   *
   */
  @Override
  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
      String sortOrder) {
    Cursor returnCursor=null;
    switch (sURIMatcher.match(uri)) {
      case PATH_CHEESE_1_INDEX: {
        // URI: content://com.abubusoft.contentprovidersample.provider/cheese
        returnCursor=dataSource.getCheeseDao().selectAll1(uri, projection, selection, selectionArgs, sortOrder);
        break;
      }
      case PATH_CHEESE_2_INDEX: {
        // URI: content://com.abubusoft.contentprovidersample.provider/cheese/${id}
        returnCursor=dataSource.getCheeseDao().selectById2(uri, projection, selection, selectionArgs, sortOrder);
        break;
      }
      default: {
        throw new IllegalArgumentException("Unknown URI for SELECT operation: " + uri);
      }
    }
    return returnCursor;
  }
 
  /**
   *
   * <h2>Supported insert operations</h2>
   * <table>
   * <tr><th>URI</th><th>DAO.METHOD</th></tr>
   * <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese</pre></td><td>{@link CheeseDaoImpl#insert0}</td></tr>
   * </table>
   *
   */
  @Override
  public Uri insert(Uri uri, ContentValues contentValues) {
    long _id=-1;
    Uri _returnURL=null;
    switch (sURIMatcher.match(uri)) {
      case PATH_CHEESE_1_INDEX: {
        _id=dataSource.getCheeseDao().insert0(uri, contentValues);
        _returnURL=Uri.withAppendedPath(uri, String.valueOf(_id));
        break;
      }
      default: {
        throw new IllegalArgumentException("Unknown URI for INSERT operation: " + uri);
      }
    }
    // log section BEGIN
    if (dataSource.isLogEnabled()) {
      Logger.info("Element is created with URI '%s'", _returnURL);
      Logger.info("Changes are notified for URI '%s'", uri);
    }
    // log section END
    getContext().getContentResolver().notifyChange(uri, null);
    return _returnURL;
  }
 
  /**
   *
   * <h2>Supported update operations</h2>
   * <table>
   * <tr><th>URI</th><th>DAO.METHOD</th></tr>
   * <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${cheese.id}</pre></td><td>{@link CheeseDaoImpl#update4}</td></tr>
   * </table>
   *
   */
  @Override
  public int update(Uri uri, ContentValues contentValues, String selection,
      String[] selectionArgs) {
    int returnRowUpdated=1;
    switch (sURIMatcher.match(uri)) {
      case PATH_CHEESE_2_INDEX: {
        // URI: content://com.abubusoft.contentprovidersample.provider/cheese/${cheese.id}
        returnRowUpdated=dataSource.getCheeseDao().update4(uri, contentValues, selection, selectionArgs);
        break;
      }
      default: {
        throw new IllegalArgumentException("Unknown URI for UPDATE operation: " + uri);
      }
    }
    // log section BEGIN
    if (dataSource.isLogEnabled()) {
      Logger.info("Changes are notified for URI %s", uri);
    }
    // log section END
    getContext().getContentResolver().notifyChange(uri, null);
    return returnRowUpdated;
  }
 
  /**
   *
   * <h2>Supported delete operations</h2>
   * <table>
   * <tr><th>URI</th><th>DAO.METHOD</th></tr>
   * <tr><td><pre>content://com.abubusoft.contentprovidersample.provider/cheese/${id}</pre></td><td>{@link CheeseDaoImpl#deleteById3}</td></tr>
   * </table>
   *
   */
  @Override
  public int delete(Uri uri, String selection, String[] selectionArgs) {
    int returnRowDeleted=-1;
    switch (sURIMatcher.match(uri)) {
      case PATH_CHEESE_2_INDEX: {
        // URI: content://com.abubusoft.contentprovidersample.provider/cheese/${id}
        returnRowDeleted=dataSource.getCheeseDao().deleteById3(uri, selection, selectionArgs);
        break;
      }
      default: {
        throw new IllegalArgumentException("Unknown URI for DELETE operation: " + uri);
      }
    }
    // log section BEGIN
    if (dataSource.isLogEnabled()) {
      Logger.info("Changes are notified for URI %s", uri);
    }
    // log section END
    getContext().getContentResolver().notifyChange(uri, null);
    return returnRowDeleted;
  }
 
  @Override
  public String getType(Uri uri) {
    switch (sURIMatcher.match(uri)) {
      case PATH_CHEESE_1_INDEX: {
        return "vnd.android.cursor.dir/vnd.com.abubusoft.contentprovidersample.provider.cheese";
      }
      case PATH_CHEESE_2_INDEX: {
        return "vnd.android.cursor.dir/vnd.com.abubusoft.contentprovidersample.provider.cheese";
      }
    }
    throw new IllegalArgumentException("Unknown URI for getType operation: " + uri);
  }
}

With Kripton, write a content provider is simple as decorated your data source with some annotations.

Some considerations:

  • the main difference between Room Library and Kripton Library in content provider argument is that Kripton generate for us the content provider.
  • The DAO’s methods in Kripton context can be any valid sign, Kripton will generate for us equivalent method that can be used in content provider context.
  • Kripton will generate for use the constants about content provider URI, table names and column names. You can see the Main Activity.

As already stated, you can find the original code on its GitHub repository.

Kripton revisited exampe is kripton-examples/PersistenceContentProviderSample.

For more information about Kripton Persistence Library:

Happy coding!

xcesco

 

Leave a Reply

Your email address will not be published. Required fields are marked *