Learning the Repository pattern, MVC architecture, and Unit testing
The second sprint continued to introduce some broad concepts, but did tie them more directly to Android development. During this sprint I began to plan the Fitness Explorer app more directly as we covered entities, the Repository pattern, the MVC architecture (Model View Controller), and Unit testing. When tasked to make an app you're usually given a base idea of what the client wants, and the translation and storage of data is extremely important; this is where entities come in. We use entities as a way to group data referring to a single object. Below is the requirement given for the Fitness Explorer app.
Entities
As you can see there are four pieces that display different sets of information. One for calories burned for the day, one for the activities done during the day, one for calories burned every day for the week, and another for activities done for the entire month and the associated calories lost with the activity. Based on that we'll need four different entities. For the first entity I needed a way to represent calories burned for the day. Since that's all that was needed I created the Calorie entity. This entity has an integer value representation for calories burned, and obviously the necessary getters and setters.
public class Calorie | |
{ | |
private int calorie; | |
public Calorie(int calorie) | |
{ | |
this.calorie = calorie; | |
} | |
public void setCalorie(int calorie) | |
{ | |
this.calorie = calorie; | |
} | |
public int getCalorie() | |
{ | |
return calorie; | |
} | |
} |
The second entity embodied any activities done over the course of the day. To encapsulate this I needed a start and end time using a java Calendar object, a String which represents the name of the activity, and the calories lost performing each activity. This is where the CalorieActivity entity comes in. It's going to extend the Calorie object so it doesn't need it's own field for calorie and just needs the dates and activity string.
public class CalorieActivity extends Calorie | |
{ | |
private String activity; | |
private Calendar startDate; | |
private Calendar endDate; | |
public CalorieActivity() | |
{ | |
startDate = Calendar.getInstance(); | |
endDate = Calendar.getInstance(); | |
} | |
public CalorieActivity(int cal, String activity, Calendar startDate, Calendar endDate) | |
{ | |
super(cal); | |
this.activity = activity; | |
this.startDate = startDate; | |
this.endDate = endDate; | |
} | |
public void setStartDate(Calendar startDate) | |
{ | |
this.startDate = startDate; | |
} | |
public Calendar getStartDate() | |
{ | |
return startDate; | |
} | |
public void setEndDate(Calendar endDate) | |
{ | |
this.endDate = endDate; | |
} | |
public Calendar getEndDate() | |
{ | |
return endDate; | |
} | |
public void setActivity(String activity) | |
{ | |
this.activity = activity; | |
} | |
public String getActivity() | |
{ | |
return activity; | |
} | |
} |
For the third entity, I needed a way to represent all of the calories lost in the past week. So I need a start time, end time, and the calories lost during that start and end time. I called this the CalorieDate entity. It has a start and end date using the java Calendar object, and extends the Calorie object from above.
public class CalorieDate extends Calorie | |
{ | |
private Calendar calendar; | |
public CalorieDate(int calorie, Calendar calendar) | |
{ | |
super(calorie); | |
this.calendar = calendar; | |
} | |
public CalorieDate() | |
{ | |
calendar = Calendar.getInstance(); | |
} | |
public void setCalendar(Calendar calendar) | |
{ | |
this.calendar = calendar; | |
} | |
public Calendar getCalendar() | |
{ | |
return calendar; | |
} | |
} |
The final entity I needed was to represent all of the activities done, associated with calories, for a month. This one was a little tricky because there can be multiple activities in a day. So I decided to create an ArrayList of CalorieActivities to represent the activities the user performed during a given day, along with a start and end date. This is the DayActivities entity.
public class DayActivities | |
{ | |
private Calendar startDate; | |
private Calendar endDate; | |
private ArrayList<CalorieActivity> activities; | |
public DayActivities() | |
{ | |
startDate = Calendar.getInstance(); | |
endDate = Calendar.getInstance(); | |
activities = new ArrayList<CalorieActivity>(); | |
} | |
public DayActivities(Calendar startDate, Calendar endDate, ArrayList act) | |
{ | |
this.startDate = startDate; | |
this.endDate = endDate; | |
activities = new ArrayList<CalorieActivity>(); | |
activities.addAll(act); | |
} | |
public int getSize() | |
{ | |
return activities.size(); | |
} | |
public void setEndDate(Calendar endDate) | |
{ | |
this.endDate = endDate; | |
} | |
public Calendar getEndDate() | |
{ | |
return endDate; | |
} | |
public void setStartDate(Calendar startDate) | |
{ | |
this.startDate = startDate; | |
} | |
public Calendar getStartDate() | |
{ | |
return startDate; | |
} | |
public void addActivity(CalorieActivity act) | |
{ | |
activities.add(act); | |
} | |
public ArrayList getActivities() | |
{ | |
return activities; | |
} | |
public CalorieActivity getSpecificActivity(int index) | |
{ | |
return activities.get(index); | |
} | |
} |
Repository pattern
Now with my entities finished I needed a way to load the user's Google Fit data into those entities. I knew eventually I would be interacting with the Google Fit API to query and aggregate the user's data, but in the meantime I could get by with an in memory data source for development / testing. This is where I would use the Repository pattern to hide the details of my data source behind an interface, thus allowing my to swap out a development MemoryFitnessRepository for the production GoogleFitRepository. See my IFitnessRepository interface below.
public interface IFitnessRepository | |
{ | |
void getCaloriesBurnedToday(Task<Calorie> onFinishedListener); | |
void getCaloriesBurnedThisWeek(Task<List<CalorieDate>> onFinishedListener); | |
void getCalorieActivitiesToday(Task<List<CalorieActivity>> onFinishedListener); | |
void getMonthDayActivities(Task<List<DayActivities>> onFinishedListener); | |
void loadActivityDataPointsAsync(Task<List<ActivityDataPoint>> onFinishedListener, Calendar startDate, Calendar endDate); | |
List<ActivityDataPoint> loadActivityDataPoints(Calendar startDate, Calendar endDate); | |
void subscribe(IRepositoryChangeListener listener); | |
void onSubscribe(IRepositoryChangeListener listener); | |
} | |
public interface Task<T> | |
{ | |
void onFinished(T data); | |
} |
See the temporary MemoryFitnessRepository below, which would only be used during development and testing.
public class MemoryFitnessRepository implements IFitnessRepository | |
{ | |
@Override | |
public void getCaloriesBurnedToday(Task<Calorie> onFinishedListener) | |
{ | |
onFinishedListener.onFinished(new Calorie(300)); | |
} | |
@Override | |
public void getCaloriesBurnedThisWeek(Task<List<CalorieDate>> onFinishedListener) | |
{ | |
ArrayList<DayActivities> weekList = new ArrayList<>(); | |
//add items to list | |
onFinishedListener.onFinished(caloriesBurnedThisWeek); | |
} | |
@Override | |
public void getCalorieActivitiesToday(Task<List<CalorieActivity>> onFinishedListener) | |
{ | |
ArrayList<CalorieActivity> list = new ArrayList<>(); | |
//add items to list | |
onFinishedListener.onFinished(list); | |
} | |
@Override | |
public void getMonthDayActivities(Task<List<DayActivities>> onFinishedListener) | |
{ | |
ArrayList<DayActivities> monthList = new ArrayList<>(); | |
//add items to list | |
onFinishedListener.onFinished(monthList); | |
} | |
@Override | |
public void loadActivityDataPointsAsync(Task<List<ActivityDataPoint>> onFinishedListener, Calendar startDate, Calendar endDate) | |
{ | |
ArrayList<ActivityDataPoint> list = new ArrayList<>(); | |
//add items to list | |
onFinishedListener.onFinished(list); | |
} | |
@Override | |
public List<ActivityDataPoint> loadActivityDataPoints(Calendar startDate, Calendar endDate) | |
{ | |
return null; | |
} | |
@Override | |
public void subscribe(IRepositoryChangeListener listener) | |
{ | |
} | |
@Override | |
public void onSubscribe(IRepositoryChangeListener listener) | |
{ | |
} | |
} |
MVC Architecture
WE used the MVC architecture for this app and it has three parts, the Model, the View, and the Controller. The Model manages the data and logic of the app, the View is what the user sees and interacts with, and the Controller accepts input and converts its commands for the Model or View. The final piece of the puzzle is the Repository, which is where the data comes from. The Model interacts with the Repository (which further interacts with the Google Fit app) to collect the user data and parse it to get it ready for the rest of the app to display. The way the Model, View, and Controller interact with each other can be slightly different across applications. In the Fitness Explorer app the Model and View can interact with each other, the Model interacts with the Controller, and the Controller interacts with the View. Since the Model interacts with the Repository this allows the Controller to update the View only when the Model is ready to be updated.
Unit Testing
Unit testing is an incredibly important part of software development, and it was a crucially important piece in the development of the Fitness Explorer app. Unit testing allows for the testing of individual units of code that you would otherwise need to test at a later time. This is useful because it allows you to be certain that everything is working perfectly before you move on to another task. For the Fitness Explorer app I did unit testing for all of the entities, as well as the repository, which allowed for easy integration of the Google Fit API into the app. Mock testing is another very important part of unit testing. Since some objects you're testing may have dependencies on other, more complex, objects, you need to find a way to simulate the behavior of the real objects. For the Fitness Explorer app I needed a repeatable test to ensure the correct methods were being called to display the data for the dashboard view. I created a test class called MockDashboardView to simulate this so that I was confident the correct methods were being called at the correct times.
public class TestDashboardModelImpl | |
{ | |
//mock method of the dashboard for the app. | |
private class MockDashboardView implements IDashboardView | |
{ | |
boolean caloriesBurnedTodayLoadedCalled = false; | |
boolean activitiesTodayCalled = false; | |
boolean caloriesBurnedThisWeekCalled = false; | |
boolean activitiesThisMonthCalled = false; | |
boolean fitNotConnectedCalled = false; | |
boolean showDataCalled = false; | |
boolean showNoDataCalled = false; | |
boolean showConnectingCalled = false; | |
@Override | |
public void caloriesBurnedTodayLoaded(Calorie cal) | |
{ | |
caloriesBurnedTodayLoadedCalled = true; | |
} | |
@Override | |
public void activitiesForTodayLoaded(List<CalorieActivity> activities) | |
{ | |
activitiesTodayCalled = true; | |
} | |
@Override | |
public void caloriesBurnedThisWeekLoaded(List<CalorieDate> activitiesThisWeek) | |
{ | |
caloriesBurnedThisWeekCalled = true; | |
} | |
@Override | |
public void activitiesThisMonthLoaded(List<DayActivities> activitiesThisMonth) | |
{ | |
activitiesThisMonthCalled = true; | |
} | |
@Override | |
public void showFitNotConnectedDialog() | |
{ | |
fitNotConnectedCalled = true; | |
} | |
@Override | |
public void showData() | |
{ | |
showDataCalled = true; | |
} | |
@Override | |
public void showNoData() | |
{ | |
showNoDataCalled = true; | |
} | |
@Override | |
public void showConnecting() | |
{ | |
showConnectingCalled = true; | |
} | |
} | |
} |
Next Sprint
After a two sprints that mostly introduced me to some high level concepts, the next sprint actually gets into the Android architecture. In the next article I'll cover my introduction to Android Views, Layouts, and the Object Pooling pattern.