Your SlideShare is downloading. ×
0
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
droidparts
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

droidparts

461

Published on

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

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

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Alex Yanchenko @ de.droidcon.com/2014 DroidParts Explained
  • 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. 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. 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" /> 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. 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 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. Yes. What If?
  • 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. App Blocks HTTP/REST JSON SQL/ORM IntentService Activity Fragment View AsyncTask EventBus Dependency Injection
  • 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. 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. 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 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. 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. 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 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. 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. 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> { 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. 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. Data Layer JSON ORM IntentService AsyncTask
  • 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. 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. 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") 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. 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. 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. 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 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. 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. 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. JSON, ORM Supported types: ● Primitives, wrapper classes. ● Enums ● JSONObject, JSONArray ● Uri ● UUID ● Drawables ● Models (JSON only), Entities ● Arrays & collections
  • 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. 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(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. Networking Layer RESTClient ImageFetcher
  • 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 + Network Layers JSON ORM IntentService AsyncTask RESTClient
  • 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. 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. 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. 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. See in action in DroidPartsGram droidparts-samples/ DroidPartsGram

×