AndroidQuery


Source link: https://github.com/FredJul/AndroidQuery

WARNING: now that Room is out, I no longer maintain that library. If you need a library to easy access to default android ContentProvider, I would maybe suggest the use of EasyContentProviders.

AndroidQuery

AndroidQuery is an Java/Kotlin Android ORM for SQLite and ContentProvider.

While you can use other good ORMs like DbFlow/Freezer/sqlitemagic/whatever/... for your local databases, the goal of AndroidQuery is to work with several solutions/language/framework without forcing you to use one of them. For example you have:

  • ContentProvider support (including some defaults models for easily accessing Android data like contacts)
  • Android Loaders support (both normal and support-v4) and ContentObserver support, even without ContentProvider
  • RxJava support (both version 1 and 2)
  • Raw queries and custom field type support
  • Kotlin support

It is also very lightweight and efficient (code generation via annotation processing).


Table of Contents


Setup

Gradle dependencies

ext.androidquery_version = '1.5.5'  dependencies {

  annotationProcessor "net.frju.androidquery:android-query-preprocessor:${
androidquery_version
}
"
  compile "net.frju.androidquery:android-query:${
androidquery_version
}
" 
}

If you want to use RxJava1 or RxJava2 you also need to add some of the following lines:

 compile 'io.reactivex:rxjava:1.2.3' // For RxJava1 and rx() method
  compile 'io.reactivex:rxandroid:1.2.1' // For RxJava1 and rx() method

compile 'io.reactivex.rxjava2:rxjava:2.0.2' // For RxJava2 and rx2() method
  compile 'io.reactivex.rxjava2:rxandroid:2.0.1' // For RxJava2 and rx2() method

Initialize the ORM

You need to initialize the ORM with a context to make it work properly. A good way to do it is by defining your Application object:

public class App extends Application {

@Override
  public void onCreate() {

super.onCreate();

 Q.init(this);

  
}
 
}

And then declare it into your AndroidManifest.xml:

<application

android:name=".App">

Define your local database and models

You first need to declare your database. A BaseLocalDatabaseProvider is using SQLite to store data.

public class LocalDatabaseProvider extends BaseLocalDatabaseProvider {

public LocalDatabaseProvider(Context context) {

super(context);

  
}

@Override
  protected String getDbName() {

return "local_models";
  
}

@Override
  protected int getDbVersion() {

return 1;
  
}

@Override
  protected Resolver getResolver() {

return Q.getResolver();

  
}

@Override
  protected void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

super.onUpgrade(db, oldVersion, newVersion);
 // by default, AndroidQuery will create new models and add new fields

 // Put here your migration code, do that directly on the db object, no AndroidQuery methods
  
}

@Override
  protected void onPostUpgrade(int oldVersion, int newVersion) {

// Here you can directly call all kind of AndroidQuery methods, including MODEL.insert()/update()/delete()...
  
}
 
}

Then models are defined by POJOs that are annotated with @DbModel. Model fields are annotated with @DbField.

Java Kotlin
@DbModel(databaseProvider = LocalDatabaseProvider.class) public class User {
 ????@DbField(index = true, dbName = "_id", ????????????primaryKey = true, autoIncrement = true) ????public int id; ????@DbField ????public String username; ????@DbField ????public long timestamp; ????@DbField ????public boolean isRegistered; ????@DbField ????public byte[] profilePicture; 
}
@DbModel(databaseProvider = LocalDatabaseProvider::class) class Feed {
 // data class not supported ????@DbField(primaryKey = true, autoIncrement = true) ????var id = 0 ????@DbField(unique = true) ????var username: String? = null 
}

Use custom types

By default AndroidQuery supports several Java/Android types, but you are not restricted to them and can define some additional types:

@TypeConverter(dbClass = String.class, modelClass = Uri.class) public class UriConverter extends BaseTypeConverter<String, Uri> {

@Override
  public String convertToDb(Uri model) {

return model == null ? null : model.toString();

  
}

@Override
  public Uri convertFromDb(String data) {

return data == null ? null : Uri.parse(data);

  
}
 
}

Supported constraints

Here are the supported constraints:

  • primary key (only on one field)
  • autoincrement
  • unique (both on one field or several thanks to the uniqueGroup attribute)
  • not null
  • foreign key

Queries

By convenience all examples of this section will be done synchronously. For asynchronous queries, please refer to the corresponding section.

Select

For a select() query you will get back a CursorResult object, which needs to be closed after use. You can use a try-with-resources statement for that:

// SELECT * FROM User; try(CursorResult<User> users = USER.select().query()) {

  int count = users.getCount();

  User secondUser = users.get(1);

  for (User user : users) {

//...
  
}
 
}
 catch (NullPointerException e) {

  // can happen if the database returned a null cursor 
}

You can also limit/order the results, or directly retrieve an array or a list from the CursorResult object:

// SELECT * FROM user ORDER BY username DESC LIMIT 10 User[] users = USER.select()

.limit(10) // you can limit or order the result

.orderByDesc(USER.USERNAME)

.query()

.toArray();
 // toList() also available, but a bit less efficient

However be careful: this is less efficient than directly using the CursorResult object since it needs to read and copy everything in memory. Calling toArray() or toList() methods will automatically close the CursorResult object for you.

Insert

User user = new User();
 user.setUsername("12345678");
 user.setIsRegistered(true);
 user.setTimestamp(System.currentTimeMillis());
  // INSERT INTO User (username, isRegistered, timestamp) VALUES ('12345678',true,632348968244);
 USER.insert(user).query();

Update

ContentValues contentValues = new ContentValues();
 contentValues.put(USER.IS_REGISTERED, true);
 contentValues.put(USER.TIMESTAMP, System.currentTimeMillis());
  // UPDATE User SET isRegistered = 'true', timestamp = '123456789' int rowsUpdated = USER.update()

.values(contentValues)

.query();

Save

USER.save(user).query();

The save() method will either insert the data if not in database or will update it, since this can be slower you should use that method only if you don't know if the data has been already inserted. You need to define a primary key in your model to be able to use the save() method.

Delete

// DELETE FROM User; int rowsDeleted = USER.delete().query();
 // delete all users, can add a where() if necessary

Count

// SELECT Count(*) FROM User; int count = USER.count().query();

Raw Query

// Raw queries; Cursor cursor = USER.raw("UPDATE User SET isRegistered = 'true', timestamp = '123456789'").query; if (cursor != null) {

  // ...
  cursor.close();
 
}

Where clauses

The Where class is used to build up the where query:

// SELECT * FROM User WHERE isRegistered = 'true'; User[] users = USER.select()

.where(Where.field(USER.IS_REGISTERED).isEqualTo(true))

.query()

.toArray();
// SELECT * FROM User WHERE username LIKE 'jo%' User[] users = USER.select()

.where(Where.field(USER.USERNAME).isLike("jo%"))

.query()

.toArray();
// SELECT * FROM User WHERE username IN ("sam","josh");
 User[] users = USER.select()

.where(Where.field(USER.USERNAME).isIn("sam", "josh"))

.query()

.toArray();
// SELECT * FROM User WHERE ((username = "sam" OR username = "angie") AND timestamp >= 1234567890);
 User[] users = USER.select()
.where(Where.field(USER.USERNAME).isEqualTo("sam")

 .or(Where.field(USER.USERNAME).isEqualTo("angie")),

 Where.field(USER.TIMESTAMP).isMoreThanOrEqualTo(1234567890))

.query()

.toArray();

Relations between models

Sometimes you want to automatically populate data of a model from another one (ie. get all posts of a user). There is actually two ways of doing so.

Variable initializer

You can initialize some variable thanks to the InitMethod annotation.

@DbModel(databaseProvider = LocalDatabaseProvider.class) public class User {

  @DbField(primaryKey = true, dbName = "_id")
  public long id;

public Post[] posts; // No @DbField annotation here. "Post" class is declared as another model.

@InitMethod
  public void initPosts() {

// Do another query here. Can call queryAndInit() if and only if there is no circular reference

posts = POST.select().where(Where.field(POST.USER_ID).isEqualTo(id)).queryAndInit();

  
}
 
}

When you call queryAndInit() or similar RxJava methods, you will actually execute all init methods. This can be noticeably slower depending of what you do inside (could even be a network request).

Be careful to not do any circular reference.

Local database and joins

If you do not need a list of sub models and if both model share the same BaseLocalDatabaseProvider (does not work with BaseContentDatabaseProvider), you could use a join. This is far more efficient than the previous method since it does not add any database request.

Joins can be performed using the innerJoin(), leftOutJoin(), crossInnerJoin(), naturalInnerJoin(), naturalLeftOuterJoin() methods. The target model for the join must be defined as an @DbField, the object will be populated with any join results.

@DbModel(databaseProvider = LocalDatabaseProvider.class) public class Comment {

  @DbField(index = true)
  public int id;
  @DbField
  public long timestamp;
  @DbField
  public int userId;
  @DbField
  public User user; // The target model for a potential join 
}
  @DbModel(databaseProvider = LocalDatabaseProvider.class) public class User {

  @DbField(index = true)
  public int id;
  @DbField
  public String username; 
}
  Comment[] comments = COMMENT.select()

.join(innerJoin(Comment.class, COMMENT.USER_ID, User.class, USER.ID))

.orderByDesc(Comment.class.getSimpleName() + '.' + COMMENT.TIMESTAMP)

.query()

.toArray();

 User user = comments[0].getUser();
 // The nested User object is populated by the join

Asynchronous queries

For an asynchronous query (to not block the UI), you can notably use RxJava (v1 or v2) or Kotlin Anko library.

Java with RxJava2 Kotlin with Anko
It is recommended to put all the returned Disposable into a CompositeDisposable and clear it inside the activity onDestroy():
private final CompositeDisposable mCompositeDisposable ????????????????= new CompositeDisposable();
 private void doQuery() {
 ????mCompositeDisposable.add(USER.select() ????????.rx2First() // we get the first user only ????????.flatMap(new Function<User, Single<CursorResult<Comment>>>() {
 ????????????@Override ????????????public Single<CursorResult<Comment>> apply(User user) ????????????????????throws Exception {
 ????????????????return COMMENT.select() ????????????????????.where(Where.field(COMMENT.USER_ID) ????????????????????.isEqualTo(user.id)) ????????????????????.rx2();
 ????????????
}
 ????????
}
) ????????.subscribe(new Consumer<CursorResult<Comment>>() {
 ????????????@Override ????????????public void accept(CursorResult<Comment> comments) ????????????????????throws Exception {
 ????????????????// do something with the comments of first user ????????????????// you are in UI thread here ????????????
}
 ????????
}
));
 
}
 @Override protected void onDestroy() {
 ????super.onDestroy();
 ????mCompositeDisposable.clear();
 
}
By default RxJava queries are always executed on Schedulers.io() and the result given on AndroidSchedulers.mainThread() unless you call the methods subscribeOn() and observeOn().
doAsync {
 ????val firstUser = USER.select() ????????.queryFirst() ????val comments = COMMENT.select() ????????.where(Where.field(COMMENT.USER_ID) ????????.isEqualTo(firstUser.id)) ????????.query() ????uiThread {
 ????????// do something with the comments of first user ????????// you are in UI thread here ????
}
 
}

Listening data changes

To listen to the data changes in your activity/fragment, you can create a Android loader this way:

public class UsersLoader extends BaseSelectLoader<User> {

public UsersLoader(Context context) {

super(context);

  
}

@Override
  public CursorResult<User> doSelect() {

return USER.select().query();

  
}
 
}
public class ExampleActivity extends Activity {

private static final int USER_LOADER_ID = 0;

private final LoaderManager.LoaderCallbacks<CursorResult<User>> mLoaderCallbacks = new LoaderManager.LoaderCallbacks<CursorResult<User>>() {

 @Override

public Loader<CursorResult<User>> onCreateLoader(int id, Bundle args) {

 UsersLoader loader = new UsersLoader(ExampleActivity.this);

 loader.setUpdateThrottle(100);

 return loader;

}

 @Override

public void onLoadFinished(Loader<CursorResult<User>> loader, CursorResult<User> data) {

 // ...

}

 @Override

public void onLoaderReset(Loader<CursorResult<User>> loader) {

 // ...

}

  
}
;

@Override
  protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

 getLoaderManager().initLoader(USER_LOADER_ID, null, mLoaderCallbacks);

  
}
 
}

You can also use a simple ContentObserver

private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {

@Override

public void onChange(boolean selfChange) {

 // ...

}

  
}
);
  // Listen to all changes (for example on your activity's onCreate()) // You can also call USER.getContentUri(model) to listen to only one model getContentResolver().registerContentObserver(USER.getContentUri(), true, mContentObserver);
  // Unregister when not needed anymore (potentially in your activity's onDestroy()) getContentResolver().unregisterContentObserver(mContentObserver);

You can instanciate an ThrottledContentObserver instead if you want to group the calls for performance reasons.

private final ContentObserver mContentObserver = new ThrottledContentObserver(new Handler(), 100) {

@Override

public void onChangeThrottled() {

 // ...

}

  
}
);

Please note that this is working even without setting any ContentProvider for your models. Be careful: you will not be notified if you modify the data with raw queries.

Database operation hooks

If you just need to maintain the data coherence or generate some default value you can inherits your model from ModelListener.

@DbModel(databaseProvider = LocalDatabaseProvider.class) public class User implements ModelListener {

@DbField(primaryKey = true)
  public String id;
  @DbField
  public long creationDate;

@Override
  public void onPreInsert() {

if (id == null) {

 id = UUID.randomUUID().toString();

}

 if (creationDate == 0) {

 creationDate = System.currentTimeMillis();

}

  
}

@Override
  public void onPreUpdate() {

  
}

@Override
  public void onPreDelete() {

  
}
 
}

Again, be careful: you will not be notified if you modify the data with raw queries.


Expose your models to an external application

Your data can also be accessed by an external application through a ContentProvider. To do so, you first need to declare the authority of your BaseLocalDatabaseProvider:

public class LocalDatabaseProvider extends BaseLocalDatabaseProvider {

@Override
  protected String getAuthority() {

return "net.frju.androidquery.sample";
  
}

...  
}

Then declare your ContentProvider with that authority:

<application
  android:name=".App">
  <provider

android:name="net.frju.androidquery.gen.USER$ContentProvider"

android:authorities="net.frju.androidquery.sample"

android:exported="true" /> </application>

If several model share the same DatabaseProvider, they will all be available with only one ContentProvider declaration.

From the external application, you will be able to access to the data either by manual ContentProvider queries, or either by setting up AndroidQuery in that project as well (see below).


Access to some external data

You can as well declare some models which are actually stored into another application. AndroidQuery will access these data through a ContentProvider.

public class UserContentDatabaseProvider extends BaseContentDatabaseProvider {

public UserContentDatabaseProvider(ContentResolver contentResolver) {

super(contentResolver);

  
}

@Override
  protected String getAuthority() {

return "net.frju.androidquery.sample"; // you want to access to models stored into the sample app
  
}

@Override
  protected Resolver getResolver() {

return Q.getResolver();

  
}
 
}
@DbModel(databaseProvider = UserContentDatabaseProvider.class) public class User {

  @DbField(primaryKey = true, dbName = "_id", autoIncrement = true)
  public int id; 
}

Then you can query that model in the same way. However, please note that raw queries and joins are not available for external model.


Default Android models

AndroidQuery also provide a library which allows you to easily access to default Android ContentProviders. You need to add android-query-models into your dependencies.

dependencies {

  annotationProcessor "net.frju.androidquery:android-query-preprocessor:${
androidquery_version
}
"
  compile "net.frju.androidquery:android-query-models:${
androidquery_version
}
" 
}

Currently the supported models are Contact, RawContact, RawContactData and BlockedNumber.

You need to add the initialization of the corresponding Q class in your Application object this way:

public class App extends Application {

@Override
  public void onCreate() {

super.onCreate();

 net.frju.androidquery.models.gen.Q.init(this);
 // for Android default models

Q.init(this);
 // only if you also have your own models
  
}
 
}

Then you can queries the lib models as you would do with your own models:

Contact[] contacts = CONTACT.select()

 .where(Where.field(CONTACT.IN_VISIBLE_GROUP).isEqualTo(true))

 .orderByAsc(CONTACT.DISPLAY_NAME, OrderBy.Collate.LOCALIZED)

 .query()

 .toArray();

TODO

  • Generate a default local database provider inside Q and allow to directly put an URI in @DbModel without having to create a ContentDatabaseProvider
  • Better migration support (like @Migration(1) function?)
  • Support for transactions
  • Support for more constraints
  • Support for Trigger
  • Better default database updater (which also adds new constraints)
  • Improve javadoc and add annotations like @NotNull
  • Support for more types by default (ArrayList, Bitmap, byte, Byte, Byte[], Set/Map, ...)
  • Support for avg, sum, min, max, ...
  • Add more android models

Resources

A painless way to pick future time.

BottomSheetPickers is a set of new time pickers for Android that can be used in place of the stock time picker.

Simple Android app for scanning and retaining barcodes.

Android library for uploading files and multipart form data easily. Fluent API interface makes API Usage easy and readable.

This project gives more control over implicit intents creation and the way it is presented.

Easy and simple implementation of PieChart with Scatter.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes