Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Alex Yanchenko @ de.droidcon.com/2014
DroidParts Explained
●
SQL operations.
●
JSON (de)serialization.
●
HTTP interactions.
●
Loading, caching, displaying images.
●
Performing work ...
Existing Solutions
Cursor query(boolean distinct, String table, String[]
columns, String selection, String[]
selectionArgs...
Existing Solutions
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(
Modifier.STATIC).create();
// By default, if ...
Existing Solutions
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/pic" />
NetworkImageView pic = (NetworkIm...
Existing Solutions
Coded past “Ballmer Peak”:
@Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retro...
●
Help handle most common tasks?
●
Uniform API?
●
Simple to make easy things easy?
●
Flexible to make hard things possible...
Yes.
What If?
●
Dependency Inection.
●
EventBus.
●
JSON (de)serialization.
●
Object-Relational Mapping.
●
AsyncTask, IntentService.
●
RE...
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"...
DependencyProvider
private final DBOpenHelper dbOpenHelper;
private PrefsManager prefsManager;
public DependencyProvider(C...
Injection Annotations
@InjectDependency - from DependencyProvider
@InjectParentActivity - in a Fragment
@InjectView(int id...
Injection in an Activity
class MyActivity extends Activity implements OnClickListener {
@InjectSystemService
private Layou...
Base Activities
class MyActivity extends Activity {
@Override
public void onPreInject() {
setContentView(R.layout.activity...
Base Activities & Fragments
DroidParts:
●
3.0+ with ActionBar features
●
Pre-3.0, no ActionBar
(.legacy.*)
DroidParts Supp...
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";
publ...
Fragment Listener
class PostListActivity extends Activity
implements PostListFragment.Listener {
@InjectFragment
private P...
EventBus
(aka MessageBus)
●
Message: Name + optional payload Object.
(not a custom class)
●
Sent from any thread, delivere...
EventBus
interface EventReceiver<T> {
void onEvent(String name, T data);
}
EventBus.postEvent(String name)
// or
EventBus....
EventBus
@ReceiveEvents(name = { "MESSAGE_SENT", "MESSAGE_RECEIVED" })
private void onMessageEvent(String eventName) {
// ...
Data Layer
JSON ORM
IntentService AsyncTask
JSON, ORM
// JSON
class JSONSerializer<ModelType extends Model> {}
// ORM
class EntityManager<EntityType extends Entity> {...
JSON
@Key(Sting name, boolean optional)
Using org.json.* under the hood.
class JSONSerializer<ModelType extends Model> {
J...
JSON
{
"author": "Alex"
"address": "http://droidparts.org",
"posts": [{
"published": 1398970584375,
"title": "Title",
}],
...
JSON
{
"sub_obj": {
"str": "val"
}
}
@Key(name = "sub_obj" + Key.SUB + "str")
String str;
@Override
public Blog deserializ...
ORM
@Table(name = "posts")
public class Post extends Entity {
@Column
public Date published;
@Column(unique = true)
public...
ORM
class Blog extends Entity {
List<Post> getPosts() {
EntityManager<Post> em = new EntityManager<Post>(
Post.class,
Inje...
EntityManager: C_UD
class Post extends Entity {}
EntityManager<Post> em =
new EntityManager<Post>(Post.class, ctx);
// Usu...
EntityManager: _R__
EntityManager<Post> em;
// 1
Select<Post> select = em.select().columns("_id", "name").
where("blog_id"...
DB Contract
interface DB {
interface Table {
}
interface Column {
String ID = BaseColumns._ID;
}
}
@Table(name = DB.Table....
DBOpenHelper
package org.droidparts.persist.sql;
class AbstractDBOpenHelper extends SQLiteOpenHelper { }
class DBOpenHelpe...
EntityCursorAdapter
class PostListAdapter extends EntityCursorAdapter<Post> {
PostListAdapter(Context ctx, Select<Post> se...
ViewHolder
class ViewHolder {
public ViewHolder(View view) {
Injector.inject(view, this);
}
}
View view = layoutInflater.i...
JSON, ORM
Supported types:
●
Primitives, wrapper classes.
●
Enums
●
JSONObject, JSONArray
●
Uri
●
UUID
●
Drawables
●
Model...
JSON, ORM
public class MyClassConverter extends Converter<MyClass> {
@Override
public boolean canHandle(Class<?> cls) { //...
Background Work
AsyncTask:
●
Ad-hoc tasks, based on user input.
●
Important if succeeded.
●
Independent, non-cancellable.
...
AsyncTask
class AsyncTask<Params, Progress, Result> extends
android.os.AsyncTask<...> {
abstract Result onExecute(Params.....
IntentService
abstract Bundle onExecute(String action, Bundle data)
throws Exception;
static Intent getIntent(Context ctx,...
Networking Layer
RESTClient
ImageFetcher
RESTClient
●
GET, PUT, POST, DELETE
●
Uses gzip|deflate compression.
●
Supports in and out headers.
●
Transparent cookies ...
RestClient
RESTClient(Context ctx)
RESTClient(Context ctx, String userAgent)
RESTClient(Context ctx, HTTPWorker worker)
RE...
RestClient2
try {
RESTClient2 client = new RESTClient2(ctx);
JSONObject obj = client.getJSONObject("http://example.
com/en...
Data + Network Layers
JSON ORM
IntentService AsyncTask
RESTClient
ImageFetcher
class ImageFetcher {
ImageFetcher(Context ctx) {
this(ctx,
new BackgroundThreadExecutor(2, "ImageFetcher-Fetc...
ImageFetcher
interface ImageReshaper {
String getCacheId();
Pair<CompressFormat, Integer> getCacheFormat(String
mimeType);...
ImageFetcher
interface ImageFetchListener {
void onFetchAdded(ImageView imageView, String imgUrl);
void onFetchProgressCha...
L(ogger)
AndroidManifest.xml:
<meta-data
android:name="droidparts_log_level"
android:value="warn" />
Object anyObj;
L.w(an...
See in action in
DroidPartsGram
droidparts-samples/
DroidPartsGram
Upcoming SlideShare
Loading in …5
×

droidparts

1,329 views

Published on

Published in: Technology, Education
  • Be the first to comment

droidparts

  1. 1. Alex Yanchenko @ de.droidcon.com/2014 DroidParts Explained
  2. 2. ● 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
  3. 3. 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)
  4. 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. 5. 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:
  6. 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. 7. ● 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?
  8. 8. Yes. What If?
  9. 9. ● Dependency Inection. ● EventBus. ● JSON (de)serialization. ● Object-Relational Mapping. ● AsyncTask, IntentService. ● RESTClient. ● ImageFetcher. ● Logger. ● Misc. DroidParts Parts 250kB, 8kLOC (v.2.0.4)
  10. 10. App Blocks HTTP/REST JSON SQL/ORM IntentService Activity Fragment View AsyncTask EventBus Dependency Injection
  11. 11. 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:
  12. 12. 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); }
  13. 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. 14. 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 } } }
  15. 15. 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:
  16. 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. 17. Base Activities & Fragments No “DroidParts” or “DP” prefix. Nice features: → SingleFragmentActivity TabbedFragmentActivity
  18. 18. 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. } }
  19. 19. 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; }
  20. 20. EventBus (aka MessageBus) ● Message: Name + optional payload Object. (not a custom class) ● Sent from any thread, delivered on the UI thread.
  21. 21. 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.
  22. 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. 23. Data Layer JSON ORM IntentService AsyncTask
  24. 24. 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:
  25. 25. 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:
  26. 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. 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. 28. 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:
  29. 29. 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:
  30. 30. 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();
  31. 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. 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. 33. 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) } }
  34. 34. 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.
  35. 35. 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); } }
  36. 36. JSON, ORM Supported types: ● Primitives, wrapper classes. ● Enums ● JSONObject, JSONArray ● Uri ● UUID ● Drawables ● Models (JSON only), Entities ● Arrays & collections
  37. 37. 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:
  38. 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. 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. 40. 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 {}
  41. 41. Networking Layer RESTClient ImageFetcher
  42. 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. 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. 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. 45. Data + Network Layers JSON ORM IntentService AsyncTask RESTClient
  46. 46. 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)
  47. 47. 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. }
  48. 48. 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); }
  49. 49. 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:
  50. 50. See in action in DroidPartsGram droidparts-samples/ DroidPartsGram

×