Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building offline first apps

Building offline first apps

GDG Blrdroid DevFest 2017

Saket Narayan

October 10, 2017
Tweet

More Decks by Saket Narayan

Other Decks in Technology

Transcript

  1. Memory leaks Lifecycle issues when using Fragments No support for

    orientation changes Response may come while Activity is minimized Problems
  2. void onSubmitClick() { LoginListener listener = (authToken) -> { database.saveAuthToken(authToken);

    startActivity(ContentFeedActivity.startIntent(this)); }; authRepository.login(username, password, listener); } The PSST way
  3. void onSubmitClick() { authRepository.login(username, password); } @Singleton public class AuthRepository

    { api.login(username, password) .doOnSuccess(authToken -> database.saveAuthToken(authToken)) .subscribe(); } The PSST way
  4. void onSubmitClick() { authRepository.login(username, password); } @Singleton public class AuthRepository

    { api.login(username, password) .doOnSuccess(authToken -> database.saveAuthToken(authToken)) .subscribe(); } The PSST way
  5. void onStart() { authRepository.streamUserSession() .takeUntil(lifecycle.onStop()) .subscribe(userSession -> { if (userSession.isLoggedIn())

    { // Start next Activity. } else { // Show login form. } }); } The PSST way Observable<UserSession>
  6. Flow of data is in a single place and not

    scattered. Every problem can be traced to an event, action, result, or model. Advantages
  7. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  8. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  9. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  10. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  11. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  12. The user is being blocked by a non-dismissible progress indicator


    Chances of losing user input on process death/phone restart. Problems
  13. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  14. void onAddNewCard(String title) {
 NetworkListener listener = () -> {


    hideProgressDialog();
 
 database.saveCard(TrelloCard.create(title));
 reloadBoardFromDatabase();
 };
 
 showProgressDialog();
 api.saveNewCard(title, listener);
 } The usual way
  15. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  16. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  17. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  18. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  19. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  20. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  21. void onUpvoteClick() { int newVoteCount = contentToShow.votes() + 1; NetworkListener

    listener = () -> { hideProgressDialog(); contentToShow = contentToShow.withVotes(newVoteCount); votesCountView.setText(newVoteCount + " points"); database.updateUserContent(contentToShow); }; showProgressDialog(); api.voteOnUserContent(contentToShow.id(), newVoteCount, listener); } The usual way
  22. Problem: UI is being driven from multiple sources void onCreate()

    { ... votesCountView.setText(contentToShow.votes() + " points”); } void onUpvoteClick() { ... votesCountView.setText(newVoteCount + " points"); ... }
  23. String id = getIntent().getStringExtra(KEY_CONTENT_ID); contentRepository.streamUserContent(id) .takeUntil(lifecycle.onDestroy()) .subscribe(userContent -> { loadImage(userContent.imageUrl());

    votesCountView.setText( userContent.votes() + " points” ); upvoteButton.setOnClickListener(o -> contentRepository.upvote(userContent) ); }); The PSST way
  24. String id = getIntent().getStringExtra(KEY_CONTENT_ID); contentRepository.streamUserContent(id) .takeUntil(lifecycle.onDestroy()) .subscribe(userContent -> { loadImage(userContent.imageUrl());

    votesCountView.setText( userContent.votes() + " points” ); upvoteButton.setOnClickListener(o -> contentRepository.upvote(userContent) ); }); The PSST way
  25. String id = getIntent().getStringExtra(KEY_CONTENT_ID); contentRepository.streamUserContent(id) .takeUntil(lifecycle.onDestroy()) .subscribe(userContent -> { loadImage(userContent.imageUrl());

    votesCountView.setText( userContent.votes() + " points” ); upvoteButton.setOnClickListener(o -> contentRepository.upvote(userContent) ); }); The PSST way
  26. String id = getIntent().getStringExtra(KEY_CONTENT_ID); contentRepository.streamUserContent(id) .takeUntil(lifecycle.onDestroy()) .subscribe(userContent -> { loadImage(userContent.imageUrl());

    votesCountView.setText( userContent.votes() + " points” ); upvoteButton.setOnClickListener(o -> contentRepository.upvote(userContent) ); }); The PSST way
  27. String id = getIntent().getStringExtra(KEY_CONTENT_ID); contentRepository.streamUserContent(id) .takeUntil(lifecycle.onDestroy()) .subscribe(userContent -> { loadImage(userContent.imageUrl());

    votesCountView.setText( userContent.votes() + " points” ); upvoteButton.setOnClickListener(o -> contentRepository.upvote(userContent) ); }); The PSST way
  28. If a network call does not involve any input validation,

    assume that it’ll succeed. Never make the user wait
  29. If a network call’s progress has to be communicated, consider

    displaying the progress state inline in UI.
 
 Totally avoid covering the screen with a progress bar. Never make the user wait
  30. void postComment(UserContent parent, String comment) { api.postComment(parent.id(), comment) .map(response ->

    ProgressState.POSTED) .onErrorReturnItem(ProgressState.FAILED) .startWith(ProgressState.IN_FLIGHT) .subscribe(progressState -> { database.createOrUpdateComment( comment, progressState ); }); } Never make the user wait
  31. void postComment(UserContent parent, String comment) { api.postComment(parent.id(), comment) .map(response ->

    ProgressState.POSTED) .onErrorReturnItem(ProgressState.FAILED) .startWith(ProgressState.IN_FLIGHT) .subscribe(progressState -> { database.createOrUpdateComment( comment, progressState ); }); } Never make the user wait
  32. void postComment(UserContent parent, String comment) { api.postComment(parent.id(), comment) .map(response ->

    ProgressState.POSTED) .onErrorReturnItem(ProgressState.FAILED) .startWith(ProgressState.IN_FLIGHT) .subscribe(progressState -> { database.createOrUpdateComment( comment, progressState ); }); } Never make the user wait
  33. void postComment(UserContent parent, String comment) { api.postComment(parent.id(), comment) .map(response ->

    ProgressState.POSTED) .onErrorReturnItem(ProgressState.FAILED) .startWith(ProgressState.IN_FLIGHT) .subscribe(progressState -> { database.createOrUpdateComment( comment, progressState ); }); } Never make the user wait
  34. void postComment(UserContent parent, String comment) { api.postComment(parent.id(), comment) .map(response ->

    ProgressState.POSTED) .onErrorReturnItem(ProgressState.FAILED) .startWith(ProgressState.IN_FLIGHT) .subscribe(progressState -> { database.createOrUpdateComment( comment, progressState ); }); } Never make the user wait
  35. contentRepository.postCommentAndStreamProgress(…) .subscribe(progressEvent -> { switch (progressEvent.state()) { case IN_FLIGHT: statusView.setText("Posting…");

    break; case POSTED: statusView.setText("Posted at " + progressEvent.timestamp()); break; case FAILED: statusView.setText("Failed. Tap to retry."); break; } }); Never make the user wait