Android With Sprinkles
Sprinkles is an android library I made because I was tired of all the boilerplate involved in dealing with SQLite on android. The goal with sprinkles is to remove as much boilerplate as possible without limiting any functionality, this is a crucial point. I find that most ORMs try to abstract SQL in a way which often leads to unoptimized queries and ugly hacks to get the more uncommon queries to work. Sprinkles lets you do what SQL is great for, querying data. Sprinkles will however help you with the tedious parts like inserting data and packing data out of a cursor.
Below I will give you a basic intro into using sprinkles by building a … note taking app! Nothing new and exiting, I know! But it’s a great sample app for showing the power of sprinkles. There’s a lot of the api that I don’t mention in this post so please head over to github and read the full documentation if you have any questions after reading this.
The app
The app will be a simple note taking app where you can create multiple notes and multiple tags which you can asign one or more of to each note. Here are some screenshots of the finished app.
I will mostly be covering the parts of the app having to do with sprinkles so I encourage you to download and play around the the full source code which you can find here.
Including the library
The easiest way to start using sprinkles is to include the library as a gradle dependecy in your build.gradle file like this.
1 2 3 |
|
The version of sprinkles used in this blog post is version 1.0.0
, remember to check the github release tab for the latest version when building your app.
If you are not using gradle (android studio defaults to using gradle) than you are probably using eclipse. If this is the case just clone the the project like this.
1
|
|
After you have cloned the library you will have to import the library folder as an android project in eclipse. Once this is done add the project as a library dependency in the project settings menu of eclipse.
Creating the models
This app is going to need two main model classes, a Note
and a Tag
. A third model that acts as the many-to-many relational link between a Note
and a Tag
is also needed (sprinkles doesn’t handle relationships for you. This is by design). Models must subclass the Model
class found in the sprinkles library. Each Model
subclass must also be annotated with a @Table
annotation which specifies the name of the table that the model class corresponds to.
Given this information we can start creating our model classes! Lets create three empty model classes. (I have removed the import statements to keep it short, this is something I will do in all future code samples)
1 2 3 4 |
|
1 2 3 4 |
|
1 2 3 4 |
|
Lets start by adding properties to our Note
model. A note will need an id, some content and timestamps for when it was created and last updated.
1 2 3 4 5 6 7 8 9 10 |
|
Sprinkles doesn’t yet know about these properties, let’s fix that. To let sprinkles know that these properties correspond to fields in the database we must annotate each property with the @Column
annotation. The @Column
annotation takes a string parameter which is the name of the column corresponding to the annotated property. We will also add a @AutoIncrementPrimaryKey annotation to the id property. This will ensure that the models id property is a unique identifier which will automatically be assigned when a new instance is inserted into the database.
1 2 3 4 5 6 7 8 9 10 11 |
|
Our Tag
model looks very similar but with a few different properties.
1 2 3 4 5 6 7 8 9 |
|
The last model which is the NoteTagLink
model introduces two new annotations. The @ForeignKey annotation marks the annotated column as a foreign key which references the table+column given as an argument to the annotation (See the sample code below for syntax, here note_id references the id column in the Notes table). The other new annotation is the @CascadeDelete annotation which may be used in conjunction with the @ForeignKey annotation to tell the underlying database to perform a cascade delete when the referenced foreign table row is deleted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
We’re soon finished with our models! Now we just need to add an easy way to update the Note
models timestamps, we will do this in the beforeCreate()
and beforeSave()
callbacks. These callbacks are declared in the Model
base class and may be overriden. There is also a afterDelete()
callback as well as a isValid()
method that can be overriden to make sure that a model that is not valid is not saved to the database, this method is overriden by the Tag
model in our sample app.
Adding the above callbacks and some setters/getters to our models give us their final form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Migrating the tables
Now that our models are done we need to create the underlying tables in the database, sprinkles makes this trivial! Start by creating an Application
subclass and registering it in your AndroidManifest.xml
. Migrating tables can be done anywhere in the app but I suggest doing it in the onCreate()
method of an Application
subclass. To register a custom Application
subclass add android:name="MY_APPLICATION_SUBCLASS"
under the application tag in your AndroidManifest.xml
.
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Time to actually migrate the tables. We will start by getting an instance of the Sprinkles
singleton. We can then create our migration adding both our models to this migration and than adding the migration to the sprinkles object. This is the most basic of migrations but all we need for this example. Sprinkles allowes you to do all the regular migrations through easy methods and also allowes for raw sql migrations when doing more complex migrations. It is important to not change or remove a migration once an app has gone into production, otherwise sprinkles won’t know which version of the database to update from/to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Listing notes
NotesActivity
is responsible for listing all the notes currently in the database. It’s an activity containing only a listview and an action bar item used to create a new list.
The following code executes a query returning all the notes in the database ordered by the time they were created.
1 2 3 |
|
The above method calls can be divided into two parts, Query.many()
and getAsync()
. Query.many()
initializes a query with a certain type of result, a Note
in the above example. The second argument is the sql query to execute, this query could contain ?
placeholders for any vararg parameters sent as the last parameters to Query.many()
. getAsync()
takes three parameters in the above example. The first is the loadermanager that will manage the loader used to execute the query and the second parameter is the callback that will receive the result of the query. The last parameter (NoteTagLink.class
is the above example) is a vararg parameter where you can pass any models that this query relies on except the queried model (Note
in this case), we pass the NoteTagLink
model into getAsync()
because we want the query to be refreshed not only when a Note
is saved/deleted but also when a NoteTagLink
is saved/deleted. Often times you can skip this last parameter.
The callback that is called when results are loaded recieves a CursorList
of the correct generic type. A CursorList
is a wrapper around a cursor so you must remember to call close()
on it at some point. Otherwise the CursorList
is very similar to a regular List
, you may iterate over it using javas enhanced for-loop and it has methods such as size()
and get(int index)
. In the callback sent to the previous query we just call swapNotes()
on the adapter which will close the adapters previous list of results and save a reference to the new list. Notice that the callback returns a boolean, in our case true
. This return value indicated whether we want to recieve further update in this callback if the underlying data ever changes, for listview data this is usually true.
1 2 3 4 5 6 7 8 9 10 |
|
Creating notes
Creating notes is done in the CreateNoteActivity
class. This is just a regular activity with an edittext and a button for saving the note. Below we set the OnClickListener
of the save button. As you can see saving a note is incredibly simple, just call save()
after setting the properties you want to set. There is also a saveAsync()
method which saves the model in the background and a version of saveAsync()
which takes a callback to notify you when the save has completed.
1 2 3 4 5 6 7 8 9 |
|
Choosing tags
I’ll skip showing you how to create tags, you can check that out yourself in the CreateTagActivity
class, it’s almost exactly like creating a note. Instead I will show you how we can set multiple tags on a note using a NoteTagLink
, this is done in the ChooseTagActivity
class. First we must query all the tags in the database. When receiving the result we swap the tags in the adapter and update what tags are checked (checked tags are attached to the note we are editing) we also return true because we want the results to update if tags are added or removed from the database.
1 2 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Next we have to query all the links between the given note and any tag, notice how we want to get updates on links when Note
and Tag
models are updated as well (the last arguments to getAsync()
). When we get the results we close the old list containing links and than update the checked tags.
1 2 3 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Ok that wasn’t too complicated! If you want to know how updateCheckedPositions()
works just check the source
. There is one last thing we need to do before we are done! When a tag is clicked we need to add/remove the corrosponding NoteTagLink
from the database. Notice how we don’t update the checked status! This is because we returned true from our ResultHandler
that queried the links, this will result in the result handler being called again when a NoteTagLink
is saved or deleted which in turn results in the checked status of the list being updated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Conclusion
So that was sprinkles, I hope you like it! Please star it on github to spread the word and fork + pull request for improvements :)
The full source code for the example app can be found on github.
This was my first post ever so please comment and give me feedback on what I can improve for the next post :)