Skip to content

Commit

Permalink
first code dump
Browse files Browse the repository at this point in the history
  • Loading branch information
emilsjolander committed Oct 13, 2013
1 parent 7d6836c commit 153d312
Show file tree
Hide file tree
Showing 60 changed files with 2,023 additions and 1 deletion.
213 changes: 212 additions & 1 deletion README.md
@@ -1,2 +1,213 @@
sprinkles
Sprinkles [![Icon](https://github.com/emilsjolander/sprinkles/raw/master/sprinkles.png)]
=========
Sprinkles is a boiler-plate-reduction-library for dealing with databases in android applications. Some would call is a kind of ORM but i don't see it that way. Sprinkles does lets SQL do what it is good at, making complex queries. SQL is however a mess (in my opinion) when is comes to everything else. This is why sprinkles helps you with things such as inserting, updated and destroying models, spinkles will also help you with the tedious task of unpacking a cursor into a model. Sprinkles actively supports version 2.3 of android and above but it should work on alder versions as well.

Download
--------
I prefer cloning my libraries and adding them as a dependency manually. This way i can easily fix a bug in the library as part of my workflow and commit it upstream (pleas do!).

However i know a lot of people like using gradle/maven so here is the dependency to add to your `build.gradle` or `pom.xml`.

```Groovy
__TODO__
```
```xml
__TODO__
```

Getting started
---------------
When you have added the library to your project add a model class to it. I will demonstrate this with a `Note.java` class. I have omitted import statements to keep it brief.
```java
@Table("Notes")
public class Note extends Model {

@AutoIncrementPrimaryKey
@Column("id")
private long id;

@Column("title")
public String title;

@Column("body")
public String body;

public long getId() {
return id;
}

}
```
Ok, a lot of important stuff in this short class. First of all. A model must subclass `se.emilsjolander.sprinkles.Model` and it also must have a `@Table` annotations specifying the table name the model corresponds to. After the class declaration we have declared three members: `id`, `title` and `body`. Notice how all of them have a `@Column` annotation to mark that they are not only a member of this class but also a column of the table that this class represents. We have one last annotation in the above example: @AutoIncrementPrimaryKey, this annotation tells sprinkles that the field is both an autoincrement field and a primary key field. A field with this annotation will automatically be set upon the creation of its corresponding row in the table.

Before using this class you must migrate it into the database. I recomend doing thing in the `onCreate()` method of an `Application` subclass like this:
```java
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();

Sprinkles sprinkles = Sprinkles.getInstance(getApplicationContext());

Migration initialMigration = new Migration();
initialMigration.createTable(Note.class);
sprinkles.addMigration(initialMigration);
}

}
```

Now you can happilty create new instances of this class and save it to the database like so:
```java
public void saveStuff() {
Note n = new Note();
n.title = "Sprinkles is awesome!";
n.body = "yup, sure is!;
n.save(); // when this call finishes n.getId() will return a valid id
}
```
You can also query for this note like this:
```java
public void queryStuff() {
Note n = Query.one("select * from Notes where title=?", "Sprinkles is awesome!").get();
}
```
There is a lot more you can do with sprinkles so please read the next section which covers the whole api!
API
---
###Annotations
- `@Table`: Used to associate a model class with a sql table.
- `@AutoIncrementPrimaryKey`: Used to mark a field a an autoincrementing primary key. The field must be an `int` or a `long` and cannot be in the same class as any other primary key.
- `@Column`: Used to associate a class field with a sql column.
- `@PrimaryKey`: Used to mark a field as a primary key. Multiple primary keys in a class are allowed and will result in a composite primary key.
- `@ForeignKey`: Used to mark a field as a foreign key. The argument given to this annotation should be foreignKeyTable(foreignKeyColumn).
- `@CascadeDelete`: Used to mark a field also marked as a foreign key as a cascade deleting field.
###Saving
The save method is both an insert and a update method, the correct thing will be done depending on if the model exists in the database or not. The two first methods below and syncronous, the second is for using together with a transaction (more on the later). There are also two asyncronous methods, one with a callback and one without. The syncronous methods will return a boolean indicating if the model was saved or not, The asyncronous method with a callback will just not invoke the callback if saving failed.
```java
boolean save();
boolean save(Transaction t);
void saveAsync();
void saveAsync(OnSavedCallback callback);
```
All the save methods use this method to check if a model exists in the database. You are free to use it as well.
```java
boolean exists();
```
###Deleting
Similar to saving there are four methods that let you delete a model. These work in the same way as save but will not return a boolean indicating the result.
```java
void delete();
void delete(Transaction t);
void deleteAsync();
void deleteAsync(OnDeletedCallback callback);
```
###Querying
Start a query with on of the following static methods:
```java
Query.One(Class clazz, String sql, Object[] args);
Query.Many(Class clazz, String sql, Object[] args);
```
Notice that unlike android built in query methods you can send in an array of objects instead of an array of strings.
Once the query has been started you can get the result with three different methods:
```java
get();
getAsync(LoaderManager lm, OnQueryResultHandler handler);
getAsyncWithUpdates(LoaderManager lm, OnQueryResultHandler handler, Class... dependencies);
```
`get()` return either the model or a list of the model represented by the `Class` you sent in as the first argument to the query method. `getAsync()` is the same only that the result is delivered on a callback function after the executeing `get()` on another thread. `getAsyncWithUpdates()` is the same as `getAsync()` only that it delivers updated results once the backing model of the query is updated. Both of the async methods use loaders and therefore need a `LoaderManager` instance. `getAsyncWithUpdates()` takes in an optional array of classes, this is used when the query relies on more models than the one you are querying for and you want the query to updated when those models change as well.
###Transactions
Both `save()` and `delete()` methods exists which take in a `Transaction`. Here is a quick example on how t use them. If any exception is thrown while saving a note or if any note fails to save the transaction will be rolled back.
```java
public void doTransaction(List<Note> notes) {
Transaction t = new Transaction();
try {
for (Note n : notes) {
if (n.save(t)) {
return;
}
}
t.setSuccessful(true);
} finally {
t.finish();
}
}
```
###Callbacks
Each model subclass can override a couple of callbacks.
Use the following callback to ensure that your model is not saved in an invalid state.
```java
@Override
public boolean isValid() {
// check model validity
}
```
Use the following callback to update a variable before the model is created
```java
@Override
protected void beforeCreate() {
mCreatedAt = System.currentTimeMillis();
}
```
Use the following callback to update a variable before the model is saved. This is called directly before `beforeCreate()` if the model is saved for the first time.
```java
@Override
protected void beforeSave() {
mUpdatedAt = System.currentTimeMillis();
}
```
User the following callback to clean up things related to the model but not stored in the databse. Perhaps a file on the internal storage?
```java
@Override
protected void afterDelete() {
// clean up some things?
}
```
###Migrations
Migrations are the way you add things to your database. I suggest putting all your migrations in the `onCreate()` method of a `Application` subclass. Here is a quick example of how that would look:
```java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Sprinkles sprinkles = Sprinkles.getInstance(getApplicationContext());
Migration initialMigration = new Migration();
initialMigration.createTable(Note.class);
sprinkles.addMigration(initialMigration);
}
}
```
The above example uses the `createTable()` method on a migration. Here is a full list of the methods it supports:
```java
void createTable(Class clazz);
void dropTable(Class clazz);
void renameTable(String from, String to);
void addColumn(Class clazz, String columnName);
void addRawStatement(String statement);
```
Any number of calls to any of the above migrations are allowed, if for example `createTable()` is called twice than two tables will be created once that migration has been added. Remember to never edit a migration, always create a new migration (this only applies to production version of the app of course).
###Relationships
Sprinkles does nothing to handle relationships for you, this is by design. You will have to use the regular ways to handle relationships in sql. Sprinkles gives you all the tools needed for this and it works very well.
4 changes: 4 additions & 0 deletions library/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.source=1.6
17 changes: 17 additions & 0 deletions library/AndroidManifest.xml
@@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="se.emilsjolander.sprinkles"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="18" />

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
</application>

</manifest>
Binary file added library/libs/android-support-v4.jar
Binary file not shown.
3 changes: 3 additions & 0 deletions library/lint.xml
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>
20 changes: 20 additions & 0 deletions library/proguard-project.txt
@@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
15 changes: 15 additions & 0 deletions library/project.properties
@@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

# Project target.
target=android-18
android.library=true
Binary file added library/res/drawable-hdpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added library/res/drawable-xhdpi/ic_launcher.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions library/res/values/strings.xml
@@ -0,0 +1,5 @@
<resources>

<string name="app_name">Cobra</string>

</resources>
20 changes: 20 additions & 0 deletions library/res/values/styles.xml
@@ -0,0 +1,20 @@
<resources>

<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>

<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>

</resources>
26 changes: 26 additions & 0 deletions library/src/se/emilsjolander/sprinkles/ColumnField.java
@@ -0,0 +1,26 @@
package se.emilsjolander.sprinkles;

import java.lang.reflect.Field;

class ColumnField {

String name;
String type;
String foreignKey;

boolean isPrimaryKey;
boolean isForeignKey;
boolean isAutoIncrementPrimaryKey;
boolean isCascadeDelete;

public Field field;

@Override
public boolean equals(Object o) {
if (o instanceof ColumnField) {
return ((ColumnField) o).name.equals(name);
}
return false;
}

}

0 comments on commit 153d312

Please sign in to comment.