50
Alex Yanchenko @ de.droidcon.com/2014 DroidParts Explained

droidparts

Embed Size (px)

Citation preview

Page 1: droidparts

Alex Yanchenko @ de.droidcon.com/2014

DroidParts Explained

Page 2: droidparts

● 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

Page 3: droidparts

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)

Page 4: droidparts

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:

Page 5: droidparts

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:

Page 6: droidparts

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

Page 7: droidparts

● 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?

Page 8: droidparts

Yes.

What If?

Page 9: droidparts

● Dependency Inection.● EventBus.● JSON (de)serialization.● Object-Relational Mapping.● AsyncTask, IntentService.● RESTClient.● ImageFetcher.● Logger.● Misc.

DroidParts Parts

250kB, 8kLOC (v.2.0.4)

Page 10: droidparts

App Blocks

HTTP/REST JSON SQL/ORM

IntentService

Activity

Fragment

View

AsyncTask

EventBusDependency

Injection

Page 11: droidparts

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:

Page 12: droidparts

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); }

Page 13: droidparts

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

Page 14: droidparts

Injection in an Activityclass 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 } }}

Page 15: droidparts

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:

Page 16: droidparts

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.*)

Page 17: droidparts

Base Activities & Fragments

No “DroidParts” or “DP” prefix.

Nice features:

SingleFragmentActivityTabbedFragmentActivity

Page 18: droidparts

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. }}

Page 19: droidparts

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;}

Page 20: droidparts

EventBus(aka MessageBus)

● Message: Name + optional payload Object.(not a custom class)

● Sent from any thread, delivered on the UI thread.

Page 21: droidparts

EventBus

interface EventReceiver<T> {

void onEvent(String name, T data);

}

EventBus.postEvent(String name)// orEventBus.postEventSticky(String name, Object data)

Send:

Receive:EventBus.registerReceiver(EventReceiver<T> receiver, String... eventNames)

// TODO Remember to unregister.

Page 22: droidparts

EventBus

@ReceiveEvents(name = { "MESSAGE_SENT", "MESSAGE_RECEIVED" })private void onMessageEvent(String eventName) { // TODO process 2 types of events without data}

@ReceiveEventsprivate void onAnyEvent(String eventName, Object eventData) { // TODO process any event with optional data}

EventBus.postEvent(String name)// orEventBus.postEventSticky(String name, Object data)

Send:

Receive in an injected class:

Page 23: droidparts

Data Layer

JSON ORM

IntentService AsyncTask

Page 24: droidparts

JSON, ORM

// JSONclass JSONSerializer<ModelType extends Model> {}

// ORMclass EntityManager<EntityType extends Entity> {}

// JSONclass Model implements Serializable {}

// ORMclass Entity extends Model {

@Column(name = "_id") public long id;}

Base Model Classes:

Managers:

Page 25: droidparts

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:

Page 26: droidparts

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 = "";}

Page 27: droidparts

JSON

{ "sub_obj": { "str": "val" }}

@Key(name = "sub_obj" + Key.SUB + "str")String str;

@Overridepublic 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:

Page 28: droidparts

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:

Page 29: droidparts

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:

Page 30: droidparts

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();

Page 31: droidparts

EntityManager: _R__

EntityManager<Post> em;

// 1Select<Post> select = em.select().columns("_id", "name"). where("blog_id", Is.EQUAL, 10);

// 2Where haveCoordinaltes = new Where("latitude", Is.NOT_EQUAL, 0).or("longitude", Is.NOT_EQUAL, 0);

em.select().where("country", Is.EQUAL, "us").where( haveCoordinates);

// 3Cursor cursor = em.select().where("author", Is.LIKE, "%%alex%%"). execute();

Select:

Page 32: droidparts

DB Contractinterface 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);

Page 33: droidparts

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) }}

Page 34: droidparts

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.

Page 35: droidparts

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); }}

Page 36: droidparts

JSON, ORM

Supported types:● Primitives, wrapper classes.● Enums● JSONObject, JSONArray● Uri● UUID● Drawables● Models (JSON only), Entities● Arrays & collections

Page 37: droidparts

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:

Page 38: droidparts

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.

Page 39: droidparts

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;}

Page 40: droidparts

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 {}

Page 41: droidparts

Networking Layer

RESTClient

ImageFetcher

Page 42: droidparts

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.

Page 43: droidparts

RestClientRESTClient(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;}

Page 44: droidparts

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; }}

Page 45: droidparts

Data + Network Layers

JSON ORM

IntentService AsyncTask

RESTClient

Page 46: droidparts

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)

Page 47: droidparts

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.}

Page 48: droidparts

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);

}

Page 49: droidparts

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:

Page 50: droidparts

See in action inDroidPartsGram

droidparts-samples/DroidPartsGram