O slideshow foi denunciado.
Seu SlideShare está sendo baixado. ×

Droidcon ES '16 - How to fail going offline

Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio

Confira estes a seguir

1 de 48 Anúncio

Mais Conteúdo rRelacionado

Diapositivos para si (20)

Quem viu também gostou (20)

Anúncio

Semelhante a Droidcon ES '16 - How to fail going offline (20)

Mais recentes (20)

Anúncio

Droidcon ES '16 - How to fail going offline

  1. 1. HOWTOFAIL GOING OFFLINE @droidpl javier.pedro@mobgen.com javierdepedrolopez@gmail.com
  2. 2. JavierdePedroLópez StrengthsExperience SeniorAndroidDeveloper 3Yearsprofessionally 2Differentcompanies Freetime architecture DesignPatterns Gradle Codequality @droidpl javier.pedro@mobgen.com javierdepedrolopez@gmail.com
  3. 3. Thistalk Try & try & fail Architecture proposal Use case Show me the code
  4. 4. TRY&TRY&FAIL Section 1
  5. 5. Try&try&fail Pojos Services SyncAdapters WhyIdidinvestigate? • I work on an SDK similar to firebase • Should work offline • Should have a simple API • Should be reliable and user friendly WhyThismatters?
  6. 6. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? • Offline improves user experience • No loading times (most of the cases) • App always accesible • Just like magic
 • Makes your app less error prone
 • Forces the developer to think more mobile
 • Say no to: “you always have a good internet connection”
  7. 7. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Views/MVP/ Interactors Using POJOS Database Callback Network Callback Pojo Callback
  8. 8. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Using POJOS • Action cancellation • Lifecycle management • Single responsibility principle broken • Messy thread management • Hard to read code
 • Fast to implement
  9. 9. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Views/MVP/ Interactors Using Services Service ResultReceiver/Binder Threading Database Sync Network Sync Pojo Callback
  10. 10. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Problems using Services • Action cancellation is still a pain • Easy to leak memory
 • Easy to split in many services • Export to other apps • Easier to handle threading • Possibility to have many processes • Simplified callback system
  11. 11. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Views/ MVP/ Interactors Using Sync Adapters Network Sync Sync adapter Sync trigger Database (content provider) Sync Content observer
  12. 12. Try&try&fail WhyThismatters? Pojos Services SyncAdapters WhyIdidinvestigate? Problems using Services • Hard disconnection errors • Sync adapter documentation • Sync adapter configuration (many files) • Too linked to accounts
 • Uses system tools • Content change notifications • All the benefits from services
  13. 13. ARCHITECTUREPROPOSAL Section 2
  14. 14. ARCHITECTUREPROPOSAL ACTLOCALLY SYNCGLOBALLY
  15. 15. LoadersArchitecture proposal Jobschedulers Alltogether Read Write Loaders Repositories Views/ MVP/ Interactors Loader inits provides data gets notified android lifecycle asks for data Data source
  16. 16. Loader statesArchitecture proposal Jobschedulers Alltogether Read Write Loaders Repositories Started Stopped Reset Can load Can observe Can deliver Can load Can observe Can deliver Can load Can observe Can deliver Stop/Reset Start/Reset Start
  17. 17. Job schedulersArchitecture proposal Jobschedulers Loaders Alltogether Read Write Repositories Pojo notifies notifies System Scheduler schedule job Preconditions check Sync Service trigger met Sync task run task
  18. 18. RepositoriesArchitecture proposal Jobschedulers Loaders Alltogether Read Write Repositories Data Repositorynotifies Database Repository Read Write Other sources Network repository Read Write
  19. 19. Architecture proposal Jobschedulers Loaders Overall diagram Alltogether Read Write Repositories Data Repository Notifies Asks for data Loader Inits Provides data System Scheduler Schedule sync Sync Service Sync task Conditions metRun task Changes data Read Write Read Write Views/ MVP/ Interactors Start App SDK write
  20. 20. Architecture proposal Jobschedulers Loaders Alltogether Read Write Repositories SDKLoader Loader Loader Overall diagram App Layer Android SDK Entry Entry Entry Entry
  21. 21. Architecture proposal Jobschedulers Loaders Read diagram Alltogether Read Write Repositories
  22. 22. Architecture proposal Jobschedulers Loaders Write diagram Alltogether Read Write Repositories online
  23. 23. USECASE Section 3
  24. 24. Usecase Onlinesample Models offlineSample Articles and comments
  25. 25. Usecase Onlinesample Models offlineSample
  26. 26. Usecase Onlinesample Models offlineSample
  27. 27. SHOWMETHECODE Section 4
  28. 28. Classes PostActivity PostLoader PostRepository PostDAO CommentDAO PostService SyncServiceSynchronizeTask write
  29. 29. PostActivity:init private PostRepository mRepository; private Executor mExecutor; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRepository = DemosApplication.instance() .demoSdk() .postRepository(); mExecutor = Executors.newSingleThreadExecutor(); startLoading(); PostLoader.init(getSupportLoaderManager(), this); } Views
  30. 30. PostActivity:loaddata @Override public Loader<List<Post>> onCreateLoader(int id, Bundle args) { return new PostLoader(this, DemosApplication.instance().demoSdk()); } @Override public void onLoadFinished(Loader<List<Post>> loader, List<Post> data) { stopLoading(); setAdapter(data); } @Override public void onLoaderReset(@NonNull Loader<List<Post>> loader) { //Do nothing } Views
  31. 31. PostActivity:Refresh @Override public void onRefresh() { startLoading(); PostLoader.getLoader(getSupportLoaderManager()).forceLoad(); } Views
  32. 32. PostActivity:Createpost @Override public void onPostCreated(@NonNull final Post post) { startLoading(); mExecutor.execute(() -> { try { mRepository.localCreate(post); } catch (RepositoryException e) { showError(); } }); } Views
  33. 33. PostActivity:Deletepost @Override public void onDeleted(@NonNull final Post post) { startLoading(); mExecutor.execute(() -> { try { mRepository.localDelete(post); } catch (RepositoryException e) { showError(); } }); } Views
  34. 34. postloader @Override public List<Post> loadInBackground() { setData(mDemoSdk.postRepository().posts()); return getData(); } @Override public void registerListener() { if (mObserver == null) { mObserver = SyncService.listenForUpdates(this); } } @Override public void unregisterListener() { if (mObserver != null) { SyncService.removeUpdateListener(this, mObserver); } } loader
  35. 35. Postrepository:allposts @WorkerThread public List<Post> posts() { Response<List<Post>> postsResponse = mPostService.posts().execute(); if (postsResponse.isSuccessful()) { List<Post> posts = postsResponse.body(); mPostDao.deleteAll(); mPostDao.save(posts); } return mPostDao.posts(); } datasource
  36. 36. Postrepository:Save @WorkerThread public void localCreate(@NonNull Post post) { mPostDao.save(post); SyncService.triggerSync(mContext); } @WorkerThread public void remoteCreate(@NonNull Post post) { Response<Post> postResponse = mPostService.create(Post.builder(post) .internalId(null) .needsSync(false) .build()).execute(); if (postResponse.isSuccessful()) { mPostDao.save(Post.builder(postResponse.body()) .internalId(post.internalId()) .build()); } } datasource
  37. 37. Postrepository:DELETE @WorkerThread public void localDelete(@NonNull Post post) { long now = new Date().getTime(); if (post.isNew() && post.isStoredLocally()) { mPostDao.delete(post.internalId()); SyncService.notifyChange(mContext); } else { mPostDao.save(Post.builder(post) .deletedAt(now) .updatedAt(now) .needsSync(true).build()); SyncService.triggerSync(mContext); } } @WorkerThread public void remoteDelete(@NonNull Post post) { if (mPostService.deletePost(post.id()).execute().isSuccessful() && post.isStoredLocally()) { for (Comment comment : mCommentDao.comments(post.internalId())) { remoteDelete(comment); } mPostDao.delete(post.internalId()); } } datasource
  38. 38. Syncservice:trigger public static void triggerSync(@NonNull Context context) { SyncService.notifyChange(context); ComponentName component = new ComponentName(context, SyncService.class); JobInfo info = new JobInfo.Builder(SYNC_SERVICE_ID, component) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .build(); getScheduler(context).schedule(info); } sync
  39. 39. Syncservice:Listenforupdates public static BroadcastReceiver listenForUpdates(@NonNull Loader loader) { IntentFilter filter = new IntentFilter(CHANGE_SYNC_INTENT_ACTION); SyncServiceReceiver receiver = new SyncServiceReceiver(loader); LocalBroadcastManager.getInstance(loader.getContext()) .registerReceiver(receiver, filter); return receiver; } public static void removeUpdateListener(@NonNull Loader loader, @NonNull BroadcastReceiver observer) { LocalBroadcastManager.getInstance(loader.getContext()) .unregisterReceiver(observer); } public static void notifyChange(@NonNull Context context) { LocalBroadcastManager.getInstance(context).sendBroadcast(getCompletionIntent()); } sync
  40. 40. Syncservice:DotheJOB @Override public boolean onStartJob(JobParameters params) { DemoSdk sdk = DemoSdk.Factory.instance(); boolean willExecute = true; if (sdk != null) { mRunningSyncTask = new SynchronizeTask(sdk, this); mRunningSyncTask.execute(params); } else { willExecute = false; } return willExecute; } sync @Override public boolean onStopJob(JobParameters params) { boolean reschedule = false; if (mRunningSyncTask != null) { mRunningSyncTask.cancel(true); reschedule = true; } return reschedule; }
  41. 41. Synctask:Sync @Override protected JobParameters doInBackground(JobParameters... params) { syncPosts(); syncComments(); return params[0]; } sync private void syncPosts() { List<Post> posts = mSdk.postRepository().localPendingPosts(); for (Post post : posts) { if (post.isNew()) { mSdk.postRepository().remoteCreate(post); } else if (post.isDeleted()) { mSdk.postRepository().remoteDelete(post); } } }
  42. 42. Synctask:NOTIFY @Override protected void onPostExecute(JobParameters jobParameters) { super.onPostExecute(jobParameters); SyncService.notifyChange(mSyncService); mSyncService.jobFinished(jobParameters, mNeedsResync); } sync
  43. 43. Andthereyougo!yourappworksoffline
  44. 44. SOURCECodeavailable https://github.com/droidpl/offline-architecture @droidpl
  45. 45. Conclusions Offline matters because UX matters It is part of the architecture Not so much effort with the right choice Unique dataflow Don’t reinvent the wheel - Android has it -
  46. 46. …………………… Wearehiring! Ask
  47. 47. …………………… Q&A
  48. 48. …………………… Thankyou!

×