application archetecture with android...

39
A simple, scalable app architecture with Android annotations Luke Sleeman Freelance Android developer lukesleeman.com.au Image CC: https://flic.kr/p/6oqCZB

Upload: dodung

Post on 29-Aug-2018

225 views

Category:

Documents


0 download

TRANSCRIPT

A simple, scalable app architecture

with Android annotations

Luke Sleeman Freelance Android developer

lukesleeman.com.au

Image CC: https://flic.kr/p/6oqCZB

Agenda• Introduction

• The architecture - an overview

• Services

• Domain object

• Testing

• UI

• Other issues

• Closing thoughts

Introduction - why we need this at all

Goals• Quick, simple

• Typically data driven apps - get some stuff from a web service, store it locally, show it to the user, submit stuff back to the web service

• Scalable - Handle small apps and big ones

• Unsurprising, easy to jump in and out of, clear and concise, makes sense to anybody. ‘Obvious’.

• Testable

• Need to be able to incrementally evolve towards it from an existing codebase

• Boring!

The architecture!

User Interface

Domain objects

Services

This is what it looks like

So what about Android annotations?

User Interface

Domain objects

Services

AndroidAnnotations

What is AndroidAnnotations?• Annotate your code - works of JDK annotations

• Code generation - you can look behind the curtain to see how the magic works. MyActivity_

• Includes annotation support for a lot of other libraries I use such as ormlite

• You don’t have to use it

• But you would be silly not to :-)

Services

User Interface

Domain objects

Services

Typical methods

List<Users> getUsersFromWebservice()

!

AndroidDatabaseResults searchForSite(String searchText)

!

UserInfo getInfoFromFacebook(int key)

A Simple Servicepublic class PizzaService {

private static List<Pizza> pizzas;

public static List<Pizza> getPizzas(){

if(pizzas == null){

pizzas = new ArrayList<Pizza>();

pizzas.add(new Pizza("Luke Special", 10.50));

pizzas.add(new Pizza("Margherita", 9.50));

}

return pizzas;

}

}

A more complex service@EBean(scope = EBean.Scope.Singleton) public class FriendService {

@OrmLiteDao(helper = ExampleDBHelper.class, model = Friend.class) protected RuntimeExceptionDao<Friend, String> friendsDao;

@RestService protected FriendListWebservice webservice;

public List<Friend> getFriends(){ return friendsDao.queryForAll(); }

public void downloadAndSaveFriend(int id)throws IOException { Friend newFriend = webservice.getFriend(id); friendsDao.create(newFriend); } }

Domain objects

User Interface

Domain objects

Services

A simple domain objectpublic class Pizza {

! private String name;

private double price;

! public Pizza(String name, double price) {

this.name = name;

this.price = price;

}

! public String getName() {

return name;

}

! public void setName(String name) {

A more complex domain object

@DatabaseTable public class Friend {

! @DatabaseField(generatedId = true) private int id;

! @DatabaseField private String userName;

! public int getId() { return id; }

! public void setId(int id) { this.id = id; }

! public String getUserName() { return userName;

Domain objects Relationships

Domain objects Relationships

• Option 1 - In the objects

@ForeignCollectionField(foreignFieldName = "user") private List<EmailAddress> addresses;

• Option 2 - In the service

public List<EmailAddress> getFriendEmails(int id)

Unit tests

User Interface

Domain objects

Services

Unit Tests

A basic testpublic class PizzaTest extends AndroidTestCase{

public void testGetPizzas(){

List<Pizza> pizzaList = PizzaService.getPizzas();

assertNotNull(pizzaList);

}

}

!public class FriendTest extends AndroidTestCase {

public void testFriend(){

FriendService friendService = FriendService_.getInstance_(getContext());

assertNotNull(friendService.getFriends());

}

}

A more advanced testpublic class FriendTest extends AndroidTestCase {

@Override protected void setUp() throws Exception { File database = getContext().getDatabasePath(ExampleDBHelper.DB_NAME); if (database.exists()) { database.delete(); } }

public void testSearchFriends(){ FriendService service = FriendService_.getInstance_(getContext()); service.createOrUpdateFriend(new Friend("Luke")); assertEquals(1, friendService.search("lu").size()); }

}

A nifty trick

public void downloadAndPatchFriendList() throws IOException{

String patchFile = downloadPatchFile(); List<Friend> updatedFriends = parsePatch(patchFile); mergeFriendsWithDB(updatedFriends);

}

A nifty trick@EBean(scope = EBean.Scope.Singleton) public class TestFriendService extends FriendService {

@Override public void mergeFriendWithDB(List<Friend> updatedFriends){ super.mergeFriendsWithDB(updatedFriends); }

@Override public List<Friend> parsePatch(String patchFile) { return super.parsePatch(patchFile); }

}

A nifty trick

private TestFriendService testFriendService = TestFriendService_.getInstance_(getContext()); ! public void testParseEmpty(){ List<Friend> friends = testFriendService.parsePatch(EMPTY_PATCH); assert... } ! public void testParseModifyFriends(){ List<Friend> friends = testFriendService.parsePatch(MODIFY_PATCH); assert... }

The UI

User Interface

Domain objects

Services

This is where android annotations really shines

• @EActivity, @EFragment, @EViewGroup

• @ViewByID @FragmentByID, etc

• @DrawableRes @AnimationRes, @HtmlRes

• @Click, @ListClick

Getting services and domain objects

@EActivity(R.layout.activity_best_friend) public class BestFriendActivity extends Activity {

@ViewById(R.id.best_friend_text) protected TextView bestFriendText;

@Bean protected FriendService friendService;

private Friend bestFriend;

@AfterViews protected void setupBestFriend(){ bestFriend = friendService.getBestFriend(); bestFriendText.setText(bestFriend.getName()); }

@Click(R.id.poke_friend_button) protected void poke(){ friendService.poke(bestFriend); }

}

Threading@EActivity(R.layout.activity_friend_list) public class FriendListActivity extends Activity {

@ViewById(R.id.friend_list) protected ListView list;

@ViewById(R.id.loading_progress) protected ProgressBar progressSpinner;

@Bean protected FriendService service;

@AfterViews protected void startDownload(){ progressSpinner.setVisibility(View.VISIBLE); downloadFriends(); }

Threading @Background protected void downloadFriends(){ try{

List<Friends> friendList = service.downloadFriendList(); displayFriends(friendList); } catch(IOException e){ displayDownloadError(); } }

@UiThread protected void displayFriends(List<Friends> friendList){ progressSpinner.setVisibility(View.GONE); … }

! @UiThread protected void displayDownloadError(){ progressSpinner.setVisibility(View.GONE); … }

}

There is a bug!

Threading @Background protected void downloadFriends(){ try{

List<Friends> friendList = service.downloadFriendList(); displayFriends(friendList); } catch(IOException e){ displayDownloadError(); } }

@UiThread protected void displayFriends(List<Friends> friendList){ progressSpinner.setVisibility(View.GONE); … }

! @UiThread protected void displayDownloadError(){ progressSpinner.setVisibility(View.GONE); … }

}

Threading - dealing with activity shutdown

• Option 1 - Let our background task run and don’t update ui

@UiThread protected void displayFriends(List<Friend> friendList){ if(isDestroyed()) return; …

Option 2 - Cancel background task@Background(id = "download") protected void downloadFriends(){ …. } @Override protected void onDestroy() { BackgroundExecutor.cancelAll("download", false); super.onDestroy(); }

Other issues

Evolving an existing apps architecture

1. Add in android annotations and clean out boilerplate

2. If you don’t have them start creating domain objects - move logic from UI into them.

3. If you don’t have them start creating services - have them mange your domain objects

Things to look at changing

• Different libraries replace AA (dagger, retrofit, butterknife)

• EventBus or RXJava

• Pinch ideas from the google io app.

• Come up with a good way to manage fragments

Closing thoughts

A humble request• Architecture is important!

• Don’t be satisfied with just the simple architecture presented …

• Think about architecture!

• Talk about architecture!

• Develop new architectures!

Questions?

Bonus slides

When not to use this architecture

• Games

• If your building one app over the course of years

• If you can develop a large internal library of reusable components