Giter Site home page Giter Site logo

yigit / dev-summit-architecture-demo Goto Github PK

View Code? Open in Web Editor NEW
1.2K 77.0 228.0 169 KB

The demo application that we've used in the Architecture Talk @ Android Dev Summit 2015

License: Apache License 2.0

Java 66.40% Ruby 24.70% JavaScript 0.28% CSS 0.78% HTML 7.84%

dev-summit-architecture-demo's Introduction

Sample Application for The Android Architecture Talk @ Android Dev Summit 2015.

video

This is a simple social sharing application where users can post text messages and also list other users' messages (feed).

It is written to demonstrate how the application can be designed to work offline and properly sync with the server as network becomes available, with minimal distraction to the user experience.

Disclaimers

This is not an official Google product.

  • To be able to easily demonstrate server failures, the sample app ships with a simple Rails server. It is mostly scaffolded, has no security etc.
  • The sample app uses many public open source projects. You should not take these as suggestions to use in your app. This demo application is written in a short time and we've used many libraries to make it a complete app as fast as possible. Since this is a demo app, we did not care much about performance characteristics of these libraries. As always, do your due diligence before using any library.
  • The models do not cache anything in memory. A real app should.
  • There is moderate testing for the project. They are not state of the art testing examples but show how different use cases can be tested.

How it works

On the topic of "offline design", many solutions depend on the particular use case but are applicable to different scenarios with small modifications. As such, this demo has its own synching logic and may not 100% match your use case. You should consider it as an example, study and then figure out how to apply a similar approach to your application. Unfortunately, there is no one fits for all solution for offline design.

Here, we'll explain how some of the user interaction flows work, which should give a better idea of what is going on in the application.

The sample project does not religiously follow any particular architectural pattern. Instead, it uses a hybrid approach that fits its own use case. It is designed with the assumption that it will grow into a large application (thus the complexity below). We chose this approach to make the demo as useful and realistic as possible despite the added complexity.

Components

  • Value Objects:
    • These objects keep data, usually backed by the database. They also know how to validate themselves so that if the server sends invalid data (hello API change!), we can ignore it before it infects the model.
  • Models:
    • These are responsible for persisting user data locally and providing methods to retrieve it.
  • Controllers:
    • These are components to keep your main application logic. They decide how to do certain things (e.g. send a new post), react to GCM etc.
  • Activities:
    • Activities control the user interface. They know where to load the data from or when to refresh themselves.
  • Events:
    • The demo application uses a global EventBus. It is the only way for the application logic to notify the UI.
    • When the UI needs something from the application logic, it makes direct calls instead. This fits the overall design of Android where UI components are more ephemeral. This flow also avoids circular dependencies between the UI and background components.
  • Jobs:
    • These are well defined operations that are typically (but not necessarily) network related. Dividing your application logic into jobs makes it much easier to test and scale. For instance, sending a new user post to the server is a Job, as well as syncing a user's feed with the server updates.

Data Flows

Sending a Post

When a user hits the send button, the first 4 steps are:

  1. Validate post.
  2. Save necessary information to persistent storage about the post (a Job in this case).
  3. Update the PostModel to include the new Post.
  4. Dispatch an event about this new Post.

(optional) 4.a. If the UI is visible, it updates itself after receiving the event.

Pay attention that the first steps did not include any steps requiring network connection yet we already have up to date information in the user interface and saved the necessary information to eventually sync the Post to server.

  1. Here are the steps for the Job:

Send Post Job Flow Diagram

  • Priority Job Queue takes care of persisting the job, back-off on failure etc so those details are omitted in the diagram.
  • A real app should also integrate with JobScheduler API to ensure pending posts are sent after application is closed.
  • When the Post Job fails, it might be a better idea to persist additional data to somewhere in your app so that next time user visits your app, you can notify them about what happened. The Demo shows a system notification if the FeedActivity is not visible.

Synchronizing Feeds

Synchronizing is managed by 3 components:

  • FeedModel:
    • Keeps track of the latest FeedItem (Post) timestamp for each feed. This timestamp is used in two places:
      • Refreshing feed so that we only receive newer items.
      • Creating the local Post. It is important that the locally created Post shows up in the feed queries. The client's clock timestamp may not match the server timestamp so until the Post is synced to server, we assign a timestamp to the new post based on the newest post timestamp of the feed.
    • Provides methods to the UI to fetch feeds from database.
  • FeedController:
    • Responsible for creating FetchFeedJob.
    • Listens for post upload failures and notifies the user with a system notification unless some other UI component (e.g. FeedActivity) handles the error first.
    • This sample app version is very basic. In a real app, this would probably handle refreshing the feed when a GCM push notification arrives and also include some logic to avoid refreshing the feed too frequently.
  • FetchFeedJob:
    • Makes the actual API call to get the latest posts for a given feed. It is responsible for updating the model and dispatch necessary events.

Keeping The UI Up to Date

The interaction between background and UI is well defined.

  • UI components make direct method calls when they need to get something done. (e.g. send a Post)
  • UI reads data only from models.
  • Background components notify events when they get something done (or fail to do so).

UI components take care of registering/unregistering to the EventBus depending on their lifecycles and since background components never directly reference UI components, we don't risk leaking them.

  • This EventBus usage usually creates some edge cases where the UI misses some events and goes out of sync. The demo application avoids these edge cases using the following rules:
    • When its lifecycle starts, it first registers for events, then loads data from the model.
    • Any event that arrives while the data is being loaded triggers another sync after data loading has completed.
    • All events arrive with a timestamp marking the oldest item in relation to that event. The UI uses this timestamp when accessing the model so that if the Items are inserted into the database in a different order, we still fetch them because the UI will use the oldest timestamp.
    • When it is stopped (e.g. Activity#onStop), it stops listening to events. If it comes back, it will do a full sync anyways so missing events in between is alright.

This is not the only way: The sample app uses a global EventBus. You can implement similar functionality using Rx or hand crafted listeners or any other similar technology. As always, do your own evaluation for your application.

Setup & Run & Tests

Setup

The demo ships with a simple server but you will need Ruby on Rails to run it. The suggested way to install ruby is through Ruby Version Manager. After installing ruby and Rails, you can start the server as follows:

> cd server;
> bundle install;
> rake db:migrate RAILS_ENV=development;

This will install the dependencies of the application and also create the database.

Running

Server

> cd server;
> rails s

Client

The demo app uses the host machine address in an emulator environment by default. (http://10.0.2.2:3000) If you run it in an emulator, it should work just fine, if not, you can change that address in the settings menu or by directly changing DemoConfig class.

Tests

  • Server: The server side does not have any tests because we simply don't care :)
  • Client: You can run the tests for the client via:
    > cd client;
    > ./gradlew clean app:connectedCheck app:test
    

MISC

Avoiding Duplicate Posts

Writing a mobile app means making peace with unreliable network. By using persistent jobs that run when network is available, the demo app does most of the work, but unfortunately it does not end there. `` Under unreliable network conditions, our application may hit a case where the data is saved in the server side but we could never received the success response, so the application still thinks the item is not posted and it will retry. Or even worse, it may happen if our server is having troubles.

Normally, this retry would mean duplication of the item. There are multiple strategies to solve this issue. The demo application uses a unique (userId, clientId) tuple to avoid duplicates. Here is how it works:

  • When client creates a post, it assigns it a unique clientId (UUID.randomUUID().toString()). This UUID, coupled with user's ID is designed to be unique both on the client side and the server side.
  • When server receives a post, it checks if the tuple already exists and if so, instead of saving a new one, simply returns the existing item.
  • When client fetches the feed, if a Post's (userId, client Id) tuple matches with an existing Post, it overrides it. This can happen if client saves a post, cannot receive the response but the Post shows up in another request's response.

You can play with these edge cases by toggling error_before_saving_post and error_after_saving_post in the server/app/controllers/posts_controller.rb.

License

Copyright (C) 2015 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

You may obtain a copy of the License at
  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and
limitations under the License.

dev-summit-architecture-demo's People

Contributors

yigit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dev-summit-architecture-demo's Issues

Rails server not running with commands rails s

I followed the steps given in readme. i am able to run client app, but rails server not starting up

c:\server>rails s
Usage:
rails new APP_PATH [options]

Options:
-r, [--ruby=PATH] # Path to the Ruby bina
ry of your choice
# Default: C:/Ruby22-x6
4/bin/ruby.exe
-m, [--template=TEMPLATE] # Path to some applicat
ion template (can be a filesystem path or URL)
[--skip-gemfile], [--no-skip-gemfile] # Don't create a Gemfil
e
-B, [--skip-bundle], [--no-skip-bundle] # Don't run bundle inst
all
-G, [--skip-git], [--no-skip-git] # Skip .gitignore file
[--skip-keeps], [--no-skip-keeps] # Skip source control .
keep files
-O, [--skip-active-record], [--no-skip-active-record] # Skip Active Record fi
les
-S, [--skip-sprockets], [--no-skip-sprockets] # Skip Sprockets files
[--skip-spring], [--no-skip-spring] # Don't install Spring
application preloader
-d, [--database=DATABASE] # Preconfigure for sele
cted database (options: mysql/oracle/postgresql/sqlite3/frontbase/ibm_db/sqlserv
er/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
# Default: sqlite3
-j, [--javascript=JAVASCRIPT] # Preconfigure for sele
cted JavaScript library
# Default: jquery
-J, [--skip-javascript], [--no-skip-javascript] # Skip JavaScript files

  [--dev], [--no-dev]                                # Setup the application

with Gemfile pointing to your Rails checkout
[--edge], [--no-edge] # Setup the application
with Gemfile pointing to Rails repository
[--skip-turbolinks], [--no-skip-turbolinks] # Skip turbolinks gem
-T, [--skip-test-unit], [--no-skip-test-unit] # Skip Test::Unit files

  [--rc=RC]                                          # Path to file containi

ng extra configuration options for rails command
[--no-rc], [--no-no-rc] # Skip loading of extra
configuration options from .railsrc file

Runtime options:
-f, [--force] # Overwrite files that already exist
-p, [--pretend], [--no-pretend] # Run but do not make any changes
-q, [--quiet], [--no-quiet] # Suppress status output
-s, [--skip], [--no-skip] # Skip files that already exist

Rails options:
-h, [--help], [--no-help] # Show this help message and quit
-v, [--version], [--no-version] # Show Rails version number and quit

Description:
The 'rails new' command creates a new Rails application with a default
directory structure and configuration at the path you specify.

You can specify extra command-line arguments to be used every time
'rails new' runs in the .railsrc configuration file in your home directory.

Note that the arguments specified in the .railsrc file don't affect the
defaults values shown above in this help message.

Example:
rails new ~/Code/Ruby/weblog

This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
See the README in the newly created application to get going.

Required SQLiteDatabase, got DatabaseWrapper Error

The following code errors in the ApplicationModule.class

@Provides
@Singleton
public SQLiteDatabase  database() {
    return FlowManager.getDatabase(DemoDatabase.NAME).getWritableDatabase();
}

with 'Required SQLiteDatabase, got DatabaseWrapper

am I missing something?

Cheers

Failed FeedFetchJob Event?

I'm having a little trouble finding where a failed fetch is handled, maybe I'm just missing it.

Here's where the activity checks for success:
public void onEventMainThread(FetchedFeedEvent event) {
if (event.isSuccess()) {
refresh(event.getOldest());
} else {
Snackbar.make(mBinding.coordinatorLayout,
R.string.cannot_refresh_feed, Snackbar.LENGTH_SHORT).show();
}

And here's where the job performs the call :
public void onRun() throws Throwable {
Response response = feed.execute();
if (response.isSuccess()) {
Post oldest = handleResponse(response.body());
mEventBus.post(new FetchedFeedEvent(true, mUserId, oldest));
} else {
throw new NetworkException(response.code());
}
}

The exception is thrown but I can't find where it's caught and translated to a failed FetchedFeedEvent.

Does FeedActivity set 'mPendingRefresh' as true ?

I thought that 'mPendingRefresh' (in FeedActivity) should be true in this callback.

mBinding.swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

@Override
public void onRefresh() {

    // mPendingRefresh = true;
    mFeedController.fetchFeedAsync(true, mUserId);
}

});

As far as I know, mPendingRefresh represents whether SwipeRefreshLayout is refreshing or not.
So it might be set as true in somewhere to prevent duplicated request for refresh.

Using this architecture in the context of an ecommerce app

Thank you for the great talk! I found the architecture very solid for the presented use cases and for this sample project. This architecture and the one presented by Virgil Dobjanschi at Google I/O 2010 are very enlightening and I reckon that using such constructs I could solve a lot of the crashes that happen today on my app.

However, the application I work on relies a lot on fresh data. We need to present the actual price of a product to the user and it can't be wrong (keeping this data in sync is already a pain and we have a lot of users complaining about divergencies). I went to study how other ecommerce apps work and even how Google Play works and they all get absolutely useless with no network.

I have been thinking and, maybe, this architecture may be useful for user generated content such as marking a product as favourite or viewing previous orders. However, I would like to hear your opinion on the subject. Do you think that there is any benefit from caching data in an app that requires up to date information? (Another use I can think of is avoiding boring serialization and deserialization of data on orientation change).

Server needs to accept at least one post to allow offline capabilities

Thanks for the great demo!

Nevertheless, I think there is an issue related to the offline capabilities of the app when
the server is not running while a first message is posted.

In this case, the new post is stored but the user is not.

https://github.com/yigit/dev-summit-architecture-demo/blob/master/client/app/src/main/java/com/android/example/devsummit/archdemo/job/post/SaveNewPostJob.java#L98

When the "NewPostEvent " event is fired and the post should be added to the list, the user is not found because it has not been saved to the datastore. As a consequence, "user" is null in the following line:

https://github.com/yigit/dev-summit-architecture-demo/blob/master/client/app/src/main/java/com/android/example/devsummit/archdemo/model/FeedModel.java#L74

Because "users" is empty, the result array is empty.

https://github.com/yigit/dev-summit-architecture-demo/blob/master/client/app/src/main/java/com/android/example/devsummit/archdemo/model/FeedModel.java#L73

So the post is never added to the list.

To get the expected behavior, the server has to run/respond at least the first time
a post is made. If this is the case, the returned user is added to the local datastore.

https://github.com/yigit/dev-summit-architecture-demo/blob/master/client/app/src/main/java/com/android/example/devsummit/archdemo/job/post/SaveNewPostJob.java#L116

In the case outlined above, this line never runs and posts never appear.

I do not think that this is intended behavior.

What do you think?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.