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

529

Published on

Published in: Technology, Education
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
529
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
10
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

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
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×