Alex Yanchenko @ de.droidcon.com/2014
DroidParts Explained
●
SQL operations.
●
JSON (de)serialization.
●
HTTP interactions.
●
Loading, caching, displaying images.
●
Performing work in background.
●
Logging.
●
Dependency management.
●
Publish/subscribe (aka Event Bus).
●
Misc repeating tasks.
Common Tasks
Existing Solutions
Cursor query(boolean distinct, String table, String[]
columns, String selection, String[]
selectionArgs, String groupBy, String having,
String orderBy, String limit);
static final String TAG = InEvery.class.getSimpleName();
Log.wtf(TAG, "Y TAG?!");
Picasso.with(context).load(url).placeholder(R.drawable.ph)
.error(R.drawable.err).into(imageView);
Repetetive code:
Not using a builder when should:
Using a builder when shouldn't:
(Fluent kills inheritance)
Existing Solutions
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(
Modifier.STATIC).create();
// By default, if you mark a field as transient, it will be
excluded.
The @DatabaseField annotation can have the following fields:
(27 ot them)
Entity user = schema.addEntity("User");
user.addIdProperty();
user.addStringProperty("name");
user.implementsSerializable();
// Then generate .java files & paste to your project.
A mess to maintain:
Abusing built-in language features:
Many features remain unused on mobile:
Existing Solutions
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/pic" />
NetworkImageView pic = (NetworkImageView)view
.findViewById(R.id.pic);
pic.setImageUrl("http://example.com/pic.png",
mImageLoader); // OMG
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://example.com/timeline.json", new
JsonHttpResponseHandler() {
@Override
public void onSuccess(JSONArray response) {
System.out.println(
"Keep calm & process JSON on the UI thread.");
}
});
Ignoring separation of concerns #2:
Ignoring separation of concerns:
Existing Solutions
Coded past “Ballmer Peak”:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
@GET("/group/{id}/users")
List<User> groupList(@Path("id") int groupId,
@QueryMap Map<String, String> options);
©xkcd
●
Help handle most common tasks?
●
Uniform API?
●
Simple to make easy things easy?
●
Flexible to make hard things possible?
●
Won't reinvent Java, OO friendly?
●
Like Django, but for Android?
What If?
Yes.
What If?
●
Dependency Inection.
●
EventBus.
●
JSON (de)serialization.
●
Object-Relational Mapping.
●
AsyncTask, IntentService.
●
RESTClient.
●
ImageFetcher.
●
Logger.
●
Misc.
DroidParts Parts
250kB, 8kLOC (v.2.0.4)
App Blocks
HTTP/REST JSON SQL/ORM
IntentService
Activity
Fragment
View
AsyncTask
EventBus
Dependency
Injection
AndroidManifest.xml:
DI Setup
<meta-data
android:name="droidparts_dependency_provider"
android:value=".DependencyProvider" />
public class DependencyProvider extends
AbstractDependencyProvider {
public DependencyProvider(Context ctx) {
super(ctx);
}
}
DependencyProvider.java:
DependencyProvider
private final DBOpenHelper dbOpenHelper;
private PrefsManager prefsManager;
public DependencyProvider(Context ctx) {
super(ctx);
dbOpenHelper = new DBOpenHelper(ctx);
}
@Override
public AbstractDBOpenHelper getDBOpenHelper() {
return dbOpenHelper;
}
public PrefsManager getPrefsManager(Context ctx) {
if (prefsManager == null) {
prefsManager = new PrefsManager(ctx);
}
return prefsManager;
}
public DialogFactory getDialogFactory(Context ctx) {
return new DialogFactory(ctx);
}
Injection Annotations
@InjectDependency - from DependencyProvider
@InjectParentActivity - in a Fragment
@InjectView(int id, boolean click) - from layout
@InjectFragment(int id) - in an Activity
@InjectResource(int value) - strings, images...
@InjectSystemService - TelephonyManager, ...
@InjectBundleExtra(String key, boolean optional)
- from Intent in an Activity
or from args in a Fragment
Injection in an Activity
class MyActivity extends Activity implements OnClickListener {
@InjectSystemService
private LayoutInflater layoutInflater;
@InjectDependency
private DialogFactory dialogFactory;
@InjectView(id = R.id.button_add, click = true)
private Button addButton;
@Override
public void onPreInject() {
setContentView(R.layout.activity_my);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // <-- Injection
}
@Override
public void onClick(View v) {
if (v == addButton) { // TODO }
}
}
Base Activities
class MyActivity extends Activity {
@Override
public void onPreInject() {
setContentView(R.layout.activity_my);
}
}
package org.droidparts.activity;
class Activity extends android.app.Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onPreInject();
Injector.inject(this); // <-- Magic
}
}
App Activity:
DroidParts Activity:
Base Activities & Fragments
DroidParts:
●
3.0+ with ActionBar features
●
Pre-3.0, no ActionBar
(.legacy.*)
DroidParts Support:
●
Pre-3.0 with Android Support Library
(.support.v4.*)
●
Pre-3.0 with ActionBarSherlock
(.sherlock.*)
●
Pre-3.0 with Android Support Library ActionBar
(.support.v7.*)
Base Activities & Fragments
No “DroidParts” or “DP” prefix.
Nice features:
→
SingleFragmentActivity
TabbedFragmentActivity
Activity Factory Methods
class PostListActivity extends Activity {
private static final String EXTRA_POSTS = "posts";
public static Intent getIntent(Context ctx,
ArrayList<Post> posts) {
Intent intent = new Intent(ctx, MyActivity.class);
intent.putExtra(EXTRA_POSTS, posts);
return intent;
}
@InjectBundleExtra(key = EXTRA_POSTS)
private ArrayList<Post> posts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO display posts.
}
}
Fragment Listener
class PostListActivity extends Activity
implements PostListFragment.Listener {
@InjectFragment
private PostListFragment fragment;
@Override
public void onPreInject() {
setContentView(R.layout.layout_with_fragments);
}
}
class PostListFragment extends ListFragment {
public interface Listener {
void doShowImageDetail(int position);
}
@InjectParentActivity
private Listener listener;
}
EventBus
(aka MessageBus)
●
Message: Name + optional payload Object.
(not a custom class)
●
Sent from any thread, delivered on the UI thread.
EventBus
interface EventReceiver<T> {
void onEvent(String name, T data);
}
EventBus.postEvent(String name)
// or
EventBus.postEventSticky(String name, Object data)
Send:
Receive:
EventBus.registerReceiver(EventReceiver<T> receiver,
String... eventNames)
// TODO Remember to unregister.
EventBus
@ReceiveEvents(name = { "MESSAGE_SENT", "MESSAGE_RECEIVED" })
private void onMessageEvent(String eventName) {
// TODO process 2 types of events without data
}
@ReceiveEvents
private void onAnyEvent(String eventName, Object eventData) {
// TODO process any event with optional data
}
EventBus.postEvent(String name)
// or
EventBus.postEventSticky(String name, Object data)
Send:
Receive in an injected class:
Data Layer
JSON ORM
IntentService AsyncTask
JSON, ORM
// JSON
class JSONSerializer<ModelType extends Model> {}
// ORM
class EntityManager<EntityType extends Entity> {}
// JSON
class Model implements Serializable {}
// ORM
class Entity extends Model {
@Column(name = "_id")
public long id;
}
Base Model Classes:
Managers:
JSON
@Key(Sting name, boolean optional)
Using org.json.* under the hood.
class JSONSerializer<ModelType extends Model> {
JSONObject serialize(ModelType item)
JSONArray serialize(Collection<ModelType> items)
ModelType deserialize(JSONObject obj)
ArrayList<ModelType> deserialize(JSONArray arr)
}
Annotation:
Manager:
JSON
{
"author": "Alex"
"address": "http://droidparts.org",
"posts": [{
"published": 1398970584375,
"title": "Title",
}],
}
class Blog extends Model {
@Key
public String author;
@Key(name="address")
public Uri uri;
@Key
public Post[] posts;
}
class Post extends Model {
@Key
public Date published;
@Key(name="title", optional=false)
public String title;
@Key(optional = true)
public String text = "";
}
JSON
{
"sub_obj": {
"str": "val"
}
}
@Key(name = "sub_obj" + Key.SUB + "str")
String str;
@Override
public Blog deserialize(JSONObject obj) throws JSONException {
Blog blog = super.deserialize(obj);
for (Post post : blog.posts) {
post.blog = blog;
}
return blog;
}
Override serialize()/deserialize() for tweaking:
Accessing nested object's property:
ORM
@Table(name = "posts")
public class Post extends Entity {
@Column
public Date published;
@Column(unique = true)
public String title;
@Column(nullable = true)
public String text = "";
@Column(eager = true)
public Blog blog;
}
@Table(String name)
@Column(String name, boolean nullable, boolean unique,
boolean eager)
Annotations:
Class example:
ORM
class Blog extends Entity {
List<Post> getPosts() {
EntityManager<Post> em = new EntityManager<Post>(
Post.class,
Injector.getApplicationContext());
Select<Post> select = em.select().where("blog_id",
Is.EQUAL, this.id);
return em.readAll(select);
}
}
Reading one-to-many:
EntityManager: C_UD
class Post extends Entity {}
EntityManager<Post> em =
new EntityManager<Post>(Post.class, ctx);
// Usually subclass & add helper methods.
Post post = new Post();
em.create(post);
assert(post.id != 0);
em.update(post);
em.createOrUpdate(post);
int postsDeleted = em.delete()
.where("year", Is.LESS, 2013)
.execute();
EntityManager: _R__
EntityManager<Post> em;
// 1
Select<Post> select = em.select().columns("_id", "name").
where("blog_id", Is.EQUAL, 10);
// 2
Where haveCoordinaltes = new Where("latitude", Is.NOT_EQUAL,
0).or("longitude", Is.NOT_EQUAL, 0);
em.select().where("country", Is.EQUAL, "us").where(
haveCoordinates);
// 3
Cursor cursor = em.select().where("author", Is.LIKE, "%%alex%%").
execute();
Select:
DB Contract
interface DB {
interface Table {
}
interface Column {
String ID = BaseColumns._ID;
}
}
@Table(name = DB.Table.BLOGS)
class Blog extends Entity {
@Column(name = DB.Column.NAME, unique = true)
String name;
}
import static org.exapmpe.app.DB.Column.*;
em.select().columns(ID, NAME).where(BLOG_ID, Is.EQUAL, 10);
DBOpenHelper
package org.droidparts.persist.sql;
class AbstractDBOpenHelper extends SQLiteOpenHelper { }
class DBOpenHelper extends AbstractDBOpenHelper {
@Override
protected void onCreateTables(SQLiteDatabase db) {
createTables(db, Blog.class, Post.class);
createIndex(db, "blogs", false, "title");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
addMissingColumns(db, Blog.class, Post.class)
}
}
EntityCursorAdapter
class PostListAdapter extends EntityCursorAdapter<Post> {
PostListAdapter(Context ctx, Select<Post> select) {
super(ctx, Post.class, select);
}
@Override
void bindView(Context context, View view, Post item) {
//TODO
}
}
Select object:
●
Has type information.
●
Can read an instance.
●
Wraps a Cursor.
●
Perfect data source for an Adapter.
ViewHolder
class ViewHolder {
public ViewHolder(View view) {
Injector.inject(view, this);
}
}
View view = layoutInflater.inflate(android.R.layout.
simple_list_item_1);
Text1Holder holder = new Text1Holder(view);
view.setTag(holder);
holder.text1.setText("Text 1");
class Text1Holder extends ViewHolder {
@InjectView(id = android.R.id.text1)
public TextView text1;
public Text1Holder(View view) {
super(view);
}
}
JSON, ORM
Supported types:
●
Primitives, wrapper classes.
●
Enums
●
JSONObject, JSONArray
●
Uri
●
UUID
●
Drawables
●
Models (JSON only), Entities
●
Arrays & collections
JSON, ORM
public class MyClassConverter extends Converter<MyClass> {
@Override
public boolean canHandle(Class<?> cls) { // TODO }
@Override
public String getDBColumnType() { // TODO }
@Override
public <V> MyClass readFromJSON(Class<MyClass> valType,
Class<V> componentType, JSONObject obj,
String key) throws JSONException { // TODO }
@Override
public <V> void putToContentValues(Class<MyClass> valueType,
Class<V> componentType, ContentValues cv,
String key, MyClass val) { // TODO }
// ...
}
ConverterRegistry.registerConverter(new MyClassConverter());
Adding new type support:
Background Work
AsyncTask:
●
Ad-hoc tasks, based on user input.
●
Important if succeeded.
●
Independent, non-cancellable.
●
Submitting a post.
IntentService:
●
Regular tasks, scheduled.
●
Not important if succeeded.
●
Sequential, cancellable.
●
Querying for new comments.
AsyncTask
class AsyncTask<Params, Progress, Result> extends
android.os.AsyncTask<...> {
abstract Result onExecute(Params... params) throws
Exception;
}
interface AsyncTaskResultListener<Result> {
void onAsyncTaskSuccess(Result result);
void onAsyncTaskFailure(Exception e);
}
AsyncTaskListener:
AsyncTask:
SimpleAsyncTask:
class SimpleAsyncTask<Result> extends
AsyncTask<Void, Integer, Result> {
abstract Result onExecute() throws Exception;
}
IntentService
abstract Bundle onExecute(String action, Bundle data)
throws Exception;
static Intent getIntent(Context ctx, Class<? extends
IntentService> cls, String action)
static Intent getIntent(Context ctx, Class<? extends
IntentService> cls, String action,
ResultReceiver resultReceiver)
public static final int RESULT_SUCCESS = Activity.RESULT_OK;
public static final int RESULT_FAILURE = Activity.
RESULT_CANCELED;
//
public static final String EXTRA_ACTION = "__action__";
public static final String EXTRA_EXCEPTION = "__exception__";
void removePendingIntents()
class IntentService extends android.app.IntentService {}
Networking Layer
RESTClient
ImageFetcher
RESTClient
●
GET, PUT, POST, DELETE
●
Uses gzip|deflate compression.
●
Supports in and out headers.
●
Transparent cookies support.
●
Http basic auth.
●
ETag & If-Modified-Since support.
●
Getting response as String or InputStream.
●
POST multipart file.
●
HttpURLConnection, Apache HTTP Client,
OkHttp workers.
RestClient
RESTClient(Context ctx)
RESTClient(Context ctx, String userAgent)
RESTClient(Context ctx, HTTPWorker worker)
RESTClient client = new RESTClient(ctx);
client.authenticateBasic("user", "pass");
client.setHeader("X-Header", "Val");
try {
HTTPResponse resp = client.get("http://example.
com/endpoint");
client.post("http://example.com/endpoint", "text/plain",
"txt");
} catch (HTTPException e) { // TODO }
class HTTPResponse {
public int code;
public Map<String, List<String>> headers;
public String body;
// or
public HTTPInputStream inputStream;
}
RestClient2
try {
RESTClient2 client = new RESTClient2(ctx);
JSONObject obj = client.getJSONObject("http://example.
com/endpoint");
JSONArray arr;
client.put("http://example.com/endpoint", arr);
} catch (HTTPException e) { // TODO }
Adds JSON support.
class HTTPException extends Exception {
public HTTPException(int respCode, String respBody) {
super(respBody);
this.respCode = respCode;
}
public int getResponseCode() {
return respCode;
}
}
Data + Network Layers
JSON ORM
IntentService AsyncTask
RESTClient
ImageFetcher
class ImageFetcher {
ImageFetcher(Context ctx) {
this(ctx,
new BackgroundThreadExecutor(2, "ImageFetcher-Fetch"),
new RESTClient(ctx),
BitmapMemoryCache.getDefaultInstance(ctx),
BitmapDiskCache.getDefaultInstance(ctx));
}
}
void attachImage(String imgUrl, ImageView imageView)
// ...
void attachImage(String imgUrl,
ImageView imageView,
ImageReshaper reshaper,
int crossFadeMillis,
ImageFetchListener listener,
Bitmap inBitmap)
ImageFetcher
interface ImageReshaper {
String getCacheId();
Pair<CompressFormat, Integer> getCacheFormat(String
mimeType);
Bitmap.Config getBitmapConfig();
int getImageWidthHint();
int getImageHeightHint();
Bitmap reshape(Bitmap bm);
}
abstract class AbstractImageReshaper implements ImageReshaper {
// TODO Check it out.
}
ImageFetcher
interface ImageFetchListener {
void onFetchAdded(ImageView imageView, String imgUrl);
void onFetchProgressChanged(ImageView imageView, String
imgUrl,
int kBTotal, int kBReceived);
void onFetchFailed(ImageView imageView, String imgUrl,
Exception e);
void onFetchCompleted(ImageView imageView, String imgUrl,
Bitmap bm);
}
L(ogger)
AndroidManifest.xml:
<meta-data
android:name="droidparts_log_level"
android:value="warn" />
Object anyObj;
L.w(anyObj);
long time = System.currentTimeMillis() - start;
L.wtf("Took %dms.", time);
DependencyProvider.<init>():39: Took 10ms.
DependencyProvider: Took 10ms.
Development mode:
Signed app:
See in action in
DroidPartsGram
droidparts-samples/
DroidPartsGram

droidparts

  • 1.
    Alex Yanchenko @de.droidcon.com/2014 DroidParts Explained
  • 2.
    ● SQL operations. ● JSON (de)serialization. ● HTTPinteractions. ● Loading, caching, displaying images. ● Performing work in background. ● Logging. ● Dependency management. ● Publish/subscribe (aka Event Bus). ● Misc repeating tasks. Common Tasks
  • 3.
    Existing Solutions Cursor query(booleandistinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); static final String TAG = InEvery.class.getSimpleName(); Log.wtf(TAG, "Y TAG?!"); Picasso.with(context).load(url).placeholder(R.drawable.ph) .error(R.drawable.err).into(imageView); Repetetive code: Not using a builder when should: Using a builder when shouldn't: (Fluent kills inheritance)
  • 4.
    Existing Solutions Gson gson= new GsonBuilder().excludeFieldsWithModifiers( Modifier.STATIC).create(); // By default, if you mark a field as transient, it will be excluded. The @DatabaseField annotation can have the following fields: (27 ot them) Entity user = schema.addEntity("User"); user.addIdProperty(); user.addStringProperty("name"); user.implementsSerializable(); // Then generate .java files & paste to your project. A mess to maintain: Abusing built-in language features: Many features remain unused on mobile:
  • 5.
    Existing Solutions <com.android.volley.toolbox.NetworkImageView android:id="@+id/pic" /> NetworkImageViewpic = (NetworkImageView)view .findViewById(R.id.pic); pic.setImageUrl("http://example.com/pic.png", mImageLoader); // OMG AsyncHttpClient client = new AsyncHttpClient(); client.get("http://example.com/timeline.json", new JsonHttpResponseHandler() { @Override public void onSuccess(JSONArray response) { System.out.println( "Keep calm & process JSON on the UI thread."); } }); Ignoring separation of concerns #2: Ignoring separation of concerns:
  • 6.
    Existing Solutions Coded past“Ballmer Peak”: @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" }) @GET("/group/{id}/users") List<User> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options); ©xkcd
  • 7.
    ● Help handle mostcommon tasks? ● Uniform API? ● Simple to make easy things easy? ● Flexible to make hard things possible? ● Won't reinvent Java, OO friendly? ● Like Django, but for Android? What If?
  • 8.
  • 9.
    ● Dependency Inection. ● EventBus. ● JSON (de)serialization. ● Object-RelationalMapping. ● AsyncTask, IntentService. ● RESTClient. ● ImageFetcher. ● Logger. ● Misc. DroidParts Parts 250kB, 8kLOC (v.2.0.4)
  • 10.
    App Blocks HTTP/REST JSONSQL/ORM IntentService Activity Fragment View AsyncTask EventBus Dependency Injection
  • 11.
    AndroidManifest.xml: DI Setup <meta-data android:name="droidparts_dependency_provider" android:value=".DependencyProvider" /> publicclass DependencyProvider extends AbstractDependencyProvider { public DependencyProvider(Context ctx) { super(ctx); } } DependencyProvider.java:
  • 12.
    DependencyProvider private final DBOpenHelperdbOpenHelper; private PrefsManager prefsManager; public DependencyProvider(Context ctx) { super(ctx); dbOpenHelper = new DBOpenHelper(ctx); } @Override public AbstractDBOpenHelper getDBOpenHelper() { return dbOpenHelper; } public PrefsManager getPrefsManager(Context ctx) { if (prefsManager == null) { prefsManager = new PrefsManager(ctx); } return prefsManager; } public DialogFactory getDialogFactory(Context ctx) { return new DialogFactory(ctx); }
  • 13.
    Injection Annotations @InjectDependency -from DependencyProvider @InjectParentActivity - in a Fragment @InjectView(int id, boolean click) - from layout @InjectFragment(int id) - in an Activity @InjectResource(int value) - strings, images... @InjectSystemService - TelephonyManager, ... @InjectBundleExtra(String key, boolean optional) - from Intent in an Activity or from args in a Fragment
  • 14.
    Injection in anActivity class MyActivity extends Activity implements OnClickListener { @InjectSystemService private LayoutInflater layoutInflater; @InjectDependency private DialogFactory dialogFactory; @InjectView(id = R.id.button_add, click = true) private Button addButton; @Override public void onPreInject() { setContentView(R.layout.activity_my); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // <-- Injection } @Override public void onClick(View v) { if (v == addButton) { // TODO } } }
  • 15.
    Base Activities class MyActivityextends Activity { @Override public void onPreInject() { setContentView(R.layout.activity_my); } } package org.droidparts.activity; class Activity extends android.app.Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); onPreInject(); Injector.inject(this); // <-- Magic } } App Activity: DroidParts Activity:
  • 16.
    Base Activities &Fragments DroidParts: ● 3.0+ with ActionBar features ● Pre-3.0, no ActionBar (.legacy.*) DroidParts Support: ● Pre-3.0 with Android Support Library (.support.v4.*) ● Pre-3.0 with ActionBarSherlock (.sherlock.*) ● Pre-3.0 with Android Support Library ActionBar (.support.v7.*)
  • 17.
    Base Activities &Fragments No “DroidParts” or “DP” prefix. Nice features: → SingleFragmentActivity TabbedFragmentActivity
  • 18.
    Activity Factory Methods classPostListActivity extends Activity { private static final String EXTRA_POSTS = "posts"; public static Intent getIntent(Context ctx, ArrayList<Post> posts) { Intent intent = new Intent(ctx, MyActivity.class); intent.putExtra(EXTRA_POSTS, posts); return intent; } @InjectBundleExtra(key = EXTRA_POSTS) private ArrayList<Post> posts; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // TODO display posts. } }
  • 19.
    Fragment Listener class PostListActivityextends Activity implements PostListFragment.Listener { @InjectFragment private PostListFragment fragment; @Override public void onPreInject() { setContentView(R.layout.layout_with_fragments); } } class PostListFragment extends ListFragment { public interface Listener { void doShowImageDetail(int position); } @InjectParentActivity private Listener listener; }
  • 20.
    EventBus (aka MessageBus) ● Message: Name+ optional payload Object. (not a custom class) ● Sent from any thread, delivered on the UI thread.
  • 21.
    EventBus interface EventReceiver<T> { voidonEvent(String name, T data); } EventBus.postEvent(String name) // or EventBus.postEventSticky(String name, Object data) Send: Receive: EventBus.registerReceiver(EventReceiver<T> receiver, String... eventNames) // TODO Remember to unregister.
  • 22.
    EventBus @ReceiveEvents(name = {"MESSAGE_SENT", "MESSAGE_RECEIVED" }) private void onMessageEvent(String eventName) { // TODO process 2 types of events without data } @ReceiveEvents private void onAnyEvent(String eventName, Object eventData) { // TODO process any event with optional data } EventBus.postEvent(String name) // or EventBus.postEventSticky(String name, Object data) Send: Receive in an injected class:
  • 23.
  • 24.
    JSON, ORM // JSON classJSONSerializer<ModelType extends Model> {} // ORM class EntityManager<EntityType extends Entity> {} // JSON class Model implements Serializable {} // ORM class Entity extends Model { @Column(name = "_id") public long id; } Base Model Classes: Managers:
  • 25.
    JSON @Key(Sting name, booleanoptional) Using org.json.* under the hood. class JSONSerializer<ModelType extends Model> { JSONObject serialize(ModelType item) JSONArray serialize(Collection<ModelType> items) ModelType deserialize(JSONObject obj) ArrayList<ModelType> deserialize(JSONArray arr) } Annotation: Manager:
  • 26.
    JSON { "author": "Alex" "address": "http://droidparts.org", "posts":[{ "published": 1398970584375, "title": "Title", }], } class Blog extends Model { @Key public String author; @Key(name="address") public Uri uri; @Key public Post[] posts; } class Post extends Model { @Key public Date published; @Key(name="title", optional=false) public String title; @Key(optional = true) public String text = ""; }
  • 27.
    JSON { "sub_obj": { "str": "val" } } @Key(name= "sub_obj" + Key.SUB + "str") String str; @Override public Blog deserialize(JSONObject obj) throws JSONException { Blog blog = super.deserialize(obj); for (Post post : blog.posts) { post.blog = blog; } return blog; } Override serialize()/deserialize() for tweaking: Accessing nested object's property:
  • 28.
    ORM @Table(name = "posts") publicclass Post extends Entity { @Column public Date published; @Column(unique = true) public String title; @Column(nullable = true) public String text = ""; @Column(eager = true) public Blog blog; } @Table(String name) @Column(String name, boolean nullable, boolean unique, boolean eager) Annotations: Class example:
  • 29.
    ORM class Blog extendsEntity { List<Post> getPosts() { EntityManager<Post> em = new EntityManager<Post>( Post.class, Injector.getApplicationContext()); Select<Post> select = em.select().where("blog_id", Is.EQUAL, this.id); return em.readAll(select); } } Reading one-to-many:
  • 30.
    EntityManager: C_UD class Postextends Entity {} EntityManager<Post> em = new EntityManager<Post>(Post.class, ctx); // Usually subclass & add helper methods. Post post = new Post(); em.create(post); assert(post.id != 0); em.update(post); em.createOrUpdate(post); int postsDeleted = em.delete() .where("year", Is.LESS, 2013) .execute();
  • 31.
    EntityManager: _R__ EntityManager<Post> em; //1 Select<Post> select = em.select().columns("_id", "name"). where("blog_id", Is.EQUAL, 10); // 2 Where haveCoordinaltes = new Where("latitude", Is.NOT_EQUAL, 0).or("longitude", Is.NOT_EQUAL, 0); em.select().where("country", Is.EQUAL, "us").where( haveCoordinates); // 3 Cursor cursor = em.select().where("author", Is.LIKE, "%%alex%%"). execute(); Select:
  • 32.
    DB Contract interface DB{ interface Table { } interface Column { String ID = BaseColumns._ID; } } @Table(name = DB.Table.BLOGS) class Blog extends Entity { @Column(name = DB.Column.NAME, unique = true) String name; } import static org.exapmpe.app.DB.Column.*; em.select().columns(ID, NAME).where(BLOG_ID, Is.EQUAL, 10);
  • 33.
    DBOpenHelper package org.droidparts.persist.sql; class AbstractDBOpenHelperextends SQLiteOpenHelper { } class DBOpenHelper extends AbstractDBOpenHelper { @Override protected void onCreateTables(SQLiteDatabase db) { createTables(db, Blog.class, Post.class); createIndex(db, "blogs", false, "title"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { addMissingColumns(db, Blog.class, Post.class) } }
  • 34.
    EntityCursorAdapter class PostListAdapter extendsEntityCursorAdapter<Post> { PostListAdapter(Context ctx, Select<Post> select) { super(ctx, Post.class, select); } @Override void bindView(Context context, View view, Post item) { //TODO } } Select object: ● Has type information. ● Can read an instance. ● Wraps a Cursor. ● Perfect data source for an Adapter.
  • 35.
    ViewHolder class ViewHolder { publicViewHolder(View view) { Injector.inject(view, this); } } View view = layoutInflater.inflate(android.R.layout. simple_list_item_1); Text1Holder holder = new Text1Holder(view); view.setTag(holder); holder.text1.setText("Text 1"); class Text1Holder extends ViewHolder { @InjectView(id = android.R.id.text1) public TextView text1; public Text1Holder(View view) { super(view); } }
  • 36.
    JSON, ORM Supported types: ● Primitives,wrapper classes. ● Enums ● JSONObject, JSONArray ● Uri ● UUID ● Drawables ● Models (JSON only), Entities ● Arrays & collections
  • 37.
    JSON, ORM public classMyClassConverter extends Converter<MyClass> { @Override public boolean canHandle(Class<?> cls) { // TODO } @Override public String getDBColumnType() { // TODO } @Override public <V> MyClass readFromJSON(Class<MyClass> valType, Class<V> componentType, JSONObject obj, String key) throws JSONException { // TODO } @Override public <V> void putToContentValues(Class<MyClass> valueType, Class<V> componentType, ContentValues cv, String key, MyClass val) { // TODO } // ... } ConverterRegistry.registerConverter(new MyClassConverter()); Adding new type support:
  • 38.
    Background Work AsyncTask: ● Ad-hoc tasks,based on user input. ● Important if succeeded. ● Independent, non-cancellable. ● Submitting a post. IntentService: ● Regular tasks, scheduled. ● Not important if succeeded. ● Sequential, cancellable. ● Querying for new comments.
  • 39.
    AsyncTask class AsyncTask<Params, Progress,Result> extends android.os.AsyncTask<...> { abstract Result onExecute(Params... params) throws Exception; } interface AsyncTaskResultListener<Result> { void onAsyncTaskSuccess(Result result); void onAsyncTaskFailure(Exception e); } AsyncTaskListener: AsyncTask: SimpleAsyncTask: class SimpleAsyncTask<Result> extends AsyncTask<Void, Integer, Result> { abstract Result onExecute() throws Exception; }
  • 40.
    IntentService abstract Bundle onExecute(Stringaction, Bundle data) throws Exception; static Intent getIntent(Context ctx, Class<? extends IntentService> cls, String action) static Intent getIntent(Context ctx, Class<? extends IntentService> cls, String action, ResultReceiver resultReceiver) public static final int RESULT_SUCCESS = Activity.RESULT_OK; public static final int RESULT_FAILURE = Activity. RESULT_CANCELED; // public static final String EXTRA_ACTION = "__action__"; public static final String EXTRA_EXCEPTION = "__exception__"; void removePendingIntents() class IntentService extends android.app.IntentService {}
  • 41.
  • 42.
    RESTClient ● GET, PUT, POST,DELETE ● Uses gzip|deflate compression. ● Supports in and out headers. ● Transparent cookies support. ● Http basic auth. ● ETag & If-Modified-Since support. ● Getting response as String or InputStream. ● POST multipart file. ● HttpURLConnection, Apache HTTP Client, OkHttp workers.
  • 43.
    RestClient RESTClient(Context ctx) RESTClient(Context ctx,String userAgent) RESTClient(Context ctx, HTTPWorker worker) RESTClient client = new RESTClient(ctx); client.authenticateBasic("user", "pass"); client.setHeader("X-Header", "Val"); try { HTTPResponse resp = client.get("http://example. com/endpoint"); client.post("http://example.com/endpoint", "text/plain", "txt"); } catch (HTTPException e) { // TODO } class HTTPResponse { public int code; public Map<String, List<String>> headers; public String body; // or public HTTPInputStream inputStream; }
  • 44.
    RestClient2 try { RESTClient2 client= new RESTClient2(ctx); JSONObject obj = client.getJSONObject("http://example. com/endpoint"); JSONArray arr; client.put("http://example.com/endpoint", arr); } catch (HTTPException e) { // TODO } Adds JSON support. class HTTPException extends Exception { public HTTPException(int respCode, String respBody) { super(respBody); this.respCode = respCode; } public int getResponseCode() { return respCode; } }
  • 45.
    Data + NetworkLayers JSON ORM IntentService AsyncTask RESTClient
  • 46.
    ImageFetcher class ImageFetcher { ImageFetcher(Contextctx) { this(ctx, new BackgroundThreadExecutor(2, "ImageFetcher-Fetch"), new RESTClient(ctx), BitmapMemoryCache.getDefaultInstance(ctx), BitmapDiskCache.getDefaultInstance(ctx)); } } void attachImage(String imgUrl, ImageView imageView) // ... void attachImage(String imgUrl, ImageView imageView, ImageReshaper reshaper, int crossFadeMillis, ImageFetchListener listener, Bitmap inBitmap)
  • 47.
    ImageFetcher interface ImageReshaper { StringgetCacheId(); Pair<CompressFormat, Integer> getCacheFormat(String mimeType); Bitmap.Config getBitmapConfig(); int getImageWidthHint(); int getImageHeightHint(); Bitmap reshape(Bitmap bm); } abstract class AbstractImageReshaper implements ImageReshaper { // TODO Check it out. }
  • 48.
    ImageFetcher interface ImageFetchListener { voidonFetchAdded(ImageView imageView, String imgUrl); void onFetchProgressChanged(ImageView imageView, String imgUrl, int kBTotal, int kBReceived); void onFetchFailed(ImageView imageView, String imgUrl, Exception e); void onFetchCompleted(ImageView imageView, String imgUrl, Bitmap bm); }
  • 49.
    L(ogger) AndroidManifest.xml: <meta-data android:name="droidparts_log_level" android:value="warn" /> Object anyObj; L.w(anyObj); longtime = System.currentTimeMillis() - start; L.wtf("Took %dms.", time); DependencyProvider.<init>():39: Took 10ms. DependencyProvider: Took 10ms. Development mode: Signed app:
  • 50.
    See in actionin DroidPartsGram droidparts-samples/ DroidPartsGram