In Open Event Organizer Android App we decided to add database migrations for every change in database while development. Two of the reasons behind this –
The users have some version of the app installed and then during development the developers had to modify the database, let’s say, add a table or modify existing ones. This makes the existing database incompatible with the new app. On adding database migration the upgradation of database takes place automatically and prevent the situation of reinstalling the app.
Migrations makes it possible to rollback or upgrade to some particular database state. Thus, help in debugging certain changes in isolation.
Let’s discuss the implementation details. Consider the scenario when a user adds a new table named SpeakersCall. For creating migration for this change we need to add migration class inside OrgaDatabase class annotated with @Database. We will break it down to look closely at each step.
Use @Migration annotation in DbFlow library and specify the new database version (by incrementing the existing version) and the database class.
Extend BaseMigration and override migrate method.
The logic used inside the migrate method can be different for different tasks. In the present case we first need to delete any existing table (if exists) with the name SpeakersCall and then recreate that table in database.
Create an array of java classes which are created/modified.
We wrap the SQL query inside a Database Wrapper class which prevents it from running recursively.
FlowManager uses reflection to look up and construct the generated database holder class used in defining the structure for all databases used in this application. So we will getModelAdapter for the class to be recreated and use creation query returned by Model Adapter and execute it.
@Migration(version = 15, database = OrgaDatabase.class)
public static class MigrationTo15 extends BaseMigration {
@Override
public void migrate(@NonNull DatabaseWrapper databaseWrapper) {
Timber.d(“Running migration for DB version 14”);
Class<?>[] recreated = new Class[] {SpeakersCall.class};
In Open Event Organizer Android App we follow Test Driven Development Approach which means the features added in the app are tested thoroughly by unit tests. More tests would ensure better code coverage and fewer bugs. This blog explains how to write tests for Viewmodel class in MVVM architecture.
Specifications
We will use JUnit4 to write unit tests and Mockito for creating mocks. The OrdersViewModel class returns the list of Order objects to the Fragment class. The objects are requested from OrderRepository class which fetches them from Network and Database. We will create a mock of OrderRepository class since it is out of context and contain logic that doesn’t depend on Orders Respository. Below is the getOrders method that we will test.
public LiveData<List<Order>> getOrders(long id, boolean reload) {
if (ordersLiveData.getValue() != null && !reload)
return ordersLiveData;
We will be using InstantTaskExecutorRule() which is a JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously. We will use setUp() method to load the RxJavaPlugins, RxAndroid plugins and reset them in tearDown method which will ensure each test runs independently from the other and avoid memory leaks. After doing this initialization and basic setup for tests we can begin code the method shouldLoadOrdersSuccessfuly() to test the getOrders method present in ViewModel class. Let’s see the step by step approach.
Use Mockito.when to return Observables one by one from ORDERS_LIST whenever the method getOrders of the mock orderRepository is called.
We will use Mockito.InOrder and pass orders, orderRepository and progress to check if they are called in a particular order.
We will use .observeForever method to observe on LiveData objects and add a ArrayList on change.
Finally, we will test and verify if the methods are called in order.
@Test
public void shouldLoadOrdersSuccessfully() {
when(orderRepository.getOrders(EVENT_ID, false))
.thenReturn(Observable.fromIterable(ORDERS_LIST));
Open Event Organizer App didn’t provide any option for the Event Organizer to view the list of Attendees present under an Order and check them in/out the event. Therefore, we designed a system such that the Organizer can just swipe the attendee present under an order to check them in or out. In this blog post, I will discuss how we implemented this functionality in Open Event Organizer App without using any third party libraries.
Specifications
We will create a separate class SwipeController.java which extends ItemTouchHelper.SimpleCallback and provide the swiping functionalities to our plain old recyclerview. We will call the super constructor with ItemTouchHelper.LEFT and ItemTouchHelper.RIGHT as arguments to provide left as well as right movements in each recyclerview list item. The bitmaps and paint object initialized here will be used later in onDraw.
Next, we will override getMovementFlags method. This method decides the allowed movement directions for each recyclerview item. The deciding logic is that, if an attendee is checked in then the allowed movement is left to check out and if an attendee is checked out then the allowed movement is right to check in. If neither of the above case, then both movements are allowed.
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;
Now after the item is swiped out or in, we need to restore its original state again. For this we override the onSwiped method and call notifyItemChanged(). Also, the changes in UI (showing green side strip for checked in and red side strip for checked out) are done by. We call the toggleCheckin() method in ViewModel to toggle the checking status of the attendee in server and local database.
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
Last but not the least, we will override the onMove method to return false. Since we are not supporting drag and drop features therefore this method will never be called.
In Open Event Organizer App, the organizer was not able to view the details for the Orders received from attendees for his/her events. So in this blog we’ll see how we implemented this functionality in the Orga App.
Specifications
There is a fragment showing the list of all orders for that event. The user will be able to click on order from the list which will then take the user to another fragment where Order details will be displayed. We will be following MVVM architecture to implement this functionality using REST API provided by Open Event Server. Let’s get started.
Firstly, we will create Order Model class. This contains various fields and relationship attributes to setup the table in database using RazizLabs DbFlow annotations.
Then, We will make a GET request to the server using Retrofit 2 to fetch Order object.
The server will return the Order details in form of a Order object and then we will save it in local database so that when there is no network connectivity then also we can show data to the user and user can refresh to fetch the latest data from network. The network observable handles fetching data from network and disk observable handles saving data in local database.
Now, we will make a Fragment class that will bind the layout file to the model in the onCreateView method using DataBindingUtil. Further, we will be observing on ViewModel to reflect changes of Order, Progress and Error objects in the UI in the onStart method of the Fragment.
public class OrderDetailFragment extends BaseFragment implements OrderDetailView {
Next, we will create OrderDetailsViewModel.This is the ViewModel class which interacts with the repository class to get data and the fragment class to show that data in UI.
Whenever the user opens Order details page, the method getOrder() twill be called which will request an Order object from OrderRepository, wrap it in MutableLiveData and provide it to Fragment.
Using MutableLiveData to hold the data makes the data reactive i.e. changes in UI are reflected automatically when the object changes. Further, we don’t have to worry handling the screen rotation as LIveData handles it all by itself.
public LiveData<Order> getOrder(String identifier, long eventId, boolean reload) {
if (orderLiveData.getValue() != null && !reload)
return orderLiveData;
In the Open Event Organizer Android App, after creating an event the organizer was unable to share it. We handled this challenge and came up with options to share Event with other social media apps. Along with that user can send Email to users containing event description with just a click. All this through a UI that our user will love interacting with. Let’s see how we implemented this.
Specifications
We designed a UI given below which offer four functionalities to the user in a single screen.
The Event Name and Date are shown in the CardView.
User can copy Event External URL by clicking on Copy URL option.
User can send Email containing information about the Event like ( Name, Description, Starting and Ending Date-Time for the Event) via Email by clicking on Email option.
User can share the same information described in point three via other social media/chatting apps etc by clicking on many more option.
The is the Event Model class ia a POJO containing the associated attributes.
We will use Retrofit to fetch Event object from server through a GET request in EventApi class.
@GET(“events/{id}?include=tickets”)
Observable<Event> getEvent(@Path(“id”) long id);
Then we will use the getEvent method of EventRepositoryImpl class to make the request for us using EventApi class and then pass on the Response Event object to the ViewModel by wrapping it in RxJava Observable. We are accepting a boolean field named reload which decdes whether we need to reuse the existing Event object from Local Database or fetch a new object from server.
In ShareEventVewModel, we are calling the getEvent from EventRepositoryImpl class, construct a LiveData object from it so that UI could observe changes on it. Methods getShareableInformation, getShareableUrl, getShareableSubject provide the shareable information to the UI which is further shared with other apps.
public class ShareEventViewModel extends ViewModel {
public String getShareableInformation() {
return Utils.getShareableInformation(event);
}
}
In ShareEventFragment class does the work of binding the UI to the model using data binding. It observes the LiveData objects supplied by presenter and reflect the changes in UI to the LiveData object.
public class ShareEventFragment extends BaseFragment implements ShareEventView {
public void shareEvent() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, shareEventViewModel.getShareableInformation());
shareIntent.setType(“text/plain”);
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
}
public void shareByEmail() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SENDTO);
shareIntent.setType(“message/rfc822”);
shareIntent.setData(Uri.parse(“mailto:”));
shareIntent.putExtra(Intent.EXTRA_SUBJECT, shareEventViewModel.getEmailSubject());
shareIntent.putExtra(Intent.EXTRA_TEXT, shareEventViewModel.getShareableInformation());
try {
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
} catch (android.content.ActivityNotFoundException ex) {
ViewUtils.showSnackbar(binding.getRoot(), “There are no email clients installed”);
}
}
public void copyUrlToClipboard() {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
String eventUrl = shareEventViewModel.getShareableUrl();
if (eventUrl == null) {
ViewUtils.showSnackbar(binding.getRoot(), “Event does not have a Public URL”);
} else {
ClipData clip = ClipData.newPlainText(“Event URL”, shareEventViewModel.getShareableUrl());
clipboard.setPrimaryClip(clip);
ViewUtils.showSnackbar(binding.getRoot(), “Event URL Copied to Clipboard”);
}
}
}
The layout file contains the Event object bind to the UI using Two way Data Binding. Here is an extract from the layout file. For viewing entire file, please refer here.
In Open Event Organizer Android App, the users were able to successfully login and sign up but in case they wanted to change their login password they could not. So, we added a feature to allow users to change their existing password. This blog explains the technical details to implement this feature following MVVM architecture and using highly efficient libraries like Retrofit, RxJava, Raziz Labs DbFlow, Data Binding.
Specifications
We will implement a page where users can enter their old password and new password along with a confirm password field. Their will be a login button to send the password change request to server. Server then return a response and we will provide feedback regarding the request. We are following MVP architecture so there will be a Model class, Fragment class, Presenter class and Network Layer to make network requests.
Let’s start with creating ChangePassword model class. There are three fields to store old password, new password and new confirmed password. Several Lombok annotations like @Data, @AllArgsConstructor, @NoArgsConstructor are used to avoid boilerplate code for getters, setters and constructors. @JsonNaming annotation is used to translate the Java Object names to KebabCase when they are serialized.
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class ChangePassword {
public String oldPassword;
public String newPassword;
@JsonIgnore
public String confirmNewPassword;
}
The layout file is binded to model using Data Binding. There will be three TextInputEditText fields for user input. An option to toggle password visibility and a login button.
The Fragment class binds layout file to the Fragment and handle UI stuff. Presenter is called to make Login request when login button is pressed.
public class ChangePasswordFragment extends BaseFragment<ChangePasswordPresenter> implements ChangePasswordView {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.change_password_fragment, container, false);
validator = new Validator(binding);
When the Login button is pressed, changePasswordRequest() method is called which makes an asynchronous call to ChangePasswordModel in order to perform the task of sending and receiving data from network in a different thread than the UI thread. Along with making requests, this method also verifies the password typed in confirm password field and send the the error as well as success message to the fragment.
public class ChangePasswordPresenter extends AbstractBasePresenter<ChangePasswordView> {
public void changePasswordRequest(String oldPassword, String newPassword, String confirmPassword) {
if (!newPassword.equals(confirmPassword)) {
getView().showError(“Passwords Do Not Match”);
return;
}
We are using Retrofit to make POST Request to server using the REST API. @Body annotation denotes the object request body which here contains a Map<String, ChangePassword> object. The Response from server is captured in Observable<ChangePasswordResponse> which is an RxJava Observable.
This is the declaration for the method in Network Layer where the actual network request is made. It takes as input the changePassword object from Presenter which is already binded with data. Then it uses RxJava to asynchronously call the Api class and pass in the Map<String, ChangePassword> object. The result is then processed and Completable object is returned to the presenter. The Presenter processes the Completable object and shows user feedback in the form of a message in SnackBar.
Open Event Android Organizer Application offered variety of features to Organizers from all over the world to help them host their Events globally but it didn’t had the functionality to create Sessions in the app itself and associate it to Tracks. This feature addition was crucial since it enables Organizer to create Sessions which every common person enquires about before attending and event. In this Blog Post we will see how we added this functionality in the app.
There can be various Sessions associated with Tracks for an Event. Open Event API had the endpoint to implement Creating Session but the Orga app didn’t, so we worked on creating a Session in the app.
The UI for creating a Session is shown above. User can fill in the necessary details and click on the green Floating Action Button to create a Session.
How to implement functionality?
We will follow MVP Architecture and use Retrofit 2.x, RxJava, Dagger, Jackson, Data Binding and other industry standard libraries to implement this functionality.
Firstly, let’s create Session model class specifying the attributes and relationships to set up in database using RazizLabs DbFlow library. The POJO will be serialized into JSON by Jackson library to be passed on as a part of RequestBody to server.
Now we will create SessionApi class that will contain the request details to be passed to Retrofit. @POST denotes a POST request and @Body denotes the requestBody of the request which is a Session object.
public interface SessionApi {
@POST(“sessions”)
Observable<Session> postSession(@Body Session session);
}
This is the CreateSessionFragment class that contains the code binding model to the view. The Fragment class implements the CreateSessionView class overriding the method declarations present there. The @Inject annotation of Dagger is used to load singleton presenter instance lazily to improve app’s performance.
Event-Id and Track-Id’s are retrieved from Bundle from Fragment Transaction. These are then passed on to presenter when Create Session button is pressed. There are other methods to show binding progressbar, snackbar and other UI components to show progress of the background request to server and database.
public class CreateSessionFragment extends BaseFragment<CreateSessionPresenter> implements CreateSessionView {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup
R.layout.session_create_layout, container, false);
validator = new Validator(binding.form);
binding.sessionCreate.setOnClickListener(view -> {
if (validator.validate()) {
getPresenter().createSession(trackId, eventId);
}
});
return binding.getRoot();
}
@Override
public void onStart() {
super.onStart();
getPresenter().attach(this);
binding.setSession(getPresenter().getSession());
}
}
In the Presenter createSession method is called when create button is pressed in UI. The method attaches track-id and event-id to Session object. This is necessary for Relationship constraints on Session Model. Then after binding all the data to Session object, we pass it on to SessionRepository. The success response is provided to user by passing success response in getView().onSuccess() method.
public class CreateSessionPresenter extends
AbstractBasePresenter<CreateSessionView> {
public Session getSession() {
return session;
}
public void createSession(long trackId, long eventId) {
Track track = new Track();
Event event = new Event();
The SessionRepository uses RxJava to make asynchronous Retrofit Call to Server. We throw a Network Error to user if the device does not have Internet Connectivity.
The session object accepted as a parameter in createSession method is passed on to sessionApi. It will return Observable<Session> Response which we will process in doOnNext() method. Then the Session object along with required foreign key relationships with Track and Event is saved in database for offline use.
The above code snippets are from Open Event Orga Application. For exploring the entire codebase please refer here. For details about the REST API used by the app please visit here.
In previous blog Using RealmRecyclerView Adapter to Show Recorded Sensor Experiments[2], I have created a DataLoggerActivity in PSLab Android app containing RecyclerView showing a list having all the recorded experiments where every list item shows the date, time and the sensor instrument used for recording the data, but there arises below questions:-
What if the user wants to see the actual recorded data in form of a graph?
How the user can see the location recorded along with the data on the map?
How can the user export that data?
There is no way I could show all of that information just on a list item so I created another activity called “SensorGraphViewActivity” the layout of that activity is shown in the figure below:
The layout contains three views:-
At the top there is graph view which I created using Android MP chart which will show the recorded data plotted on it forming the exact same curve that was created while recording it, this way it is useful to visualize the data and also there is also a play button on the top which simulates the data as it was being plotted on the graph in real time.
In the middle, there is a Card view containing two rows which will simply show the date and time of recording.
At the bottom, there is a Map view which shows the location of the user which would be fetched when the user recorded the data.
This is the gist of the layout file created for the activity.
But now the question arises:-
How to show the data in the activity for the item that the user wanted?
For that, I implemented click listener on every list item by simply adding it inside the onBindViewHolder() method
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
SensorLogged temp = getItem(position);
holder.sensor.setText(temp.getSensor());
Date date = new Date(temp.getDateTimeStart());
holder.dateTime.setText(String.valueOf(sdf.format(date)));
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
...
});
}
and inside the click listener I performed following three steps:-
First I stored the position of the item clicked inside a variable.
int positionVar = holder.getAdapterPosition();
Then I used that position from the variable to fetch related data from the Realm database by simply using getItem() method which returns the SensorLogged[1] RealmObject at that position as I used a special type of RecyclerView Adapter called as RealmRecyclerViewAdapter[2].
int positionVar = holder.getAdapterPosition();
Then I created an Intent to open the SensorGraphViewActivity and passed the related data (i.e., sensortype, foreignkey, latitude, longitude, timezone, date, time) from SensorLogged[1] object to activity in form of extras.
And, in the SensorGraphViewActivity, I used getIntent() method to fetch all those extra data in the form of Bundle. For showing the data in the graph I used the foreign key fetched from the intent and queried all the LuxData[1] RealmObject containing that foreignkey in the form of RealmResult<LuxData>[2] ArrayList and used that list to populate the graph.
Long foreignKey = intent.getLongExtra(DATA_FOREIGN_KEY, -1);
Realm realm = Realm.getDefaultInstance();
entries = new ArrayList<>();
RealmResults<LuxData> results = realm.where(LuxData.class).equalTo(DATA_FOREIGN_KEY, foreignKey).findAll();
for (LuxData item : results) {
entries.add(new Entry((float) item.getTimeElapsed(), item.getLux()));
}
For the map, I fetched the latitude and longitude again from the intent and used the coordinates to show the location on the open street view map.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
IMapController mapController = map.getController();
mapController.setZoom((double) 9);
GeoPoint startPoint = new GeoPoint(latitude, latitude);
mapController.setCenter(startPoint);
}
});
For map purposes, of course, I used a separate thread as it is a heavy and time-consuming process and it could lead the app to lag for a long time which could hamper the User Experience.
Thus after the data being plotted on the map and coordinated being plotted on the map, we can see the layout of the activity as shown in Figure 2.
I also created the export button in the toolbar that will use the CSVLogger[3] class implemented inside the PSLab android app to export the data in the form of CSV file and save it in the external storage directory.
This blog demonstrates how to pass values of a variable between two fragments of a single activity. The blog will mainly include the demonstration of passing values between fragments while using BottomSheet Navigation as done in PSLab Android application.
This blog contains the work done by me in the Lux Meter instrument of the PSLab Android app of passing data from LuxMeterConfiguration fragment to LuxMeterData fragment as shown in the featured image to set the high limit for the pointer and to set the update period of the Lux Sensor. The blog will solve the difficult task of communication between two fragments of a single activity. For passing data between multiple fragments of different activities, refer to [1].
How to pass data between fragments?
In this blog, I will pass data from Fragment 2 to Fragment 1 only. But vice versa or passing data from both the fragments can also be made using the same given approach.
First, make a static method in Fragment 1 which can set the parameters i.e. the value of the variables as soon as the fragment is inflated as follow
publicstaticvoidsetParameters(int one, int two, int three){
Fragment1.firstValue = one;
Fragment1.secondValue = two;
Fragment1.thirdValue = three;
}
Now, there is one point to mark that Fragment 1 will be inflated only when Fragment 2 gets destroyed. Else, other than default inflation of Fragment 1, there is no way Fragment 1 can be inflated after navigating to Fragment 2.
So, override the OnDestroy() method of Fragment 2 and use the setParameters() method to set the value of variables from Fragment 2 to be used in Fragment 1.
Here, the highValue, updatePeriodValue and selectedSensor are the variables being used in the Lux Meter fragment in PSLab Android app. But they can be replaced by the necessary variables as per the app.
So, in this way, we can pass data between the fragments of the same Activity in an Android application. Above demonstration can be extended in passing values between multiple fragments of the same Activity by creating different methods in different fragments.
This blog demonstrates how to prevent the Android Activity in the background from operating while the Bottom Sheet is up in the foreground. The demonstration will be purely from the work I have done under PR #1355 in PSLab Android repository.
Why prevent the Activity from operating?
When using Bottom Sheet in Android, it is preferable to dim the screen behind the Bottom Sheet to provide a good user experience. But the dimming of the screen is itself an indication that the screen won’t work. Also, if the Bottom Sheet is open and while sliding it, if, by mistake, any button in the background of the bottom sheet gets pressed, then if the function related to that button starts executing then it can create a bad user experience.
For example, in PSLab Android app, in Accelerometer instrument, there are record/pause and delete buttons in the toolbar as shown in figure 1. Now, if the bottom sheet is opened and while closing it if the delete button is by mistake pressed by the user, then whole recorded data gets deleted. Thus, it’s a good practice to prevent the background Activity from operating while Bottom Sheet is opened.
Figure 1. Accelerometer Instrument in PSLab Android app
How to prevent the Activity from operating?
In this demonstration, I will use the method followed by PSLab Android app in creating a Bottom Sheet and making the background dim using a View widget. A step by step guide on how to make a Bottom Sheet as in PSLab Android app can be found in [1] and [2].
Strategy
The strategy used in solving this problem is setting an OnClickListener to the View that is used to dim the background and close the Bottom Sheet (if open) and hide the View as soon as the method is called. The View is again made visible when an upward slide gesture is made to open the Bottom Sheet.
Follow the below steps to get the desired results:
First, in OnCreate() method, set the OnTouchListener to the view.
So, now test the Bottom Sheet and you will find that the Bottom Sheet will get closed as soon as the click is made outside it if it is opened. The demonstration of the working of the above code is shown in figure 2.
Figure 2. Demonstration of preventing the background Activity from operating while Bottom Sheet is up
You must be logged in to post a comment.