Performing Database Migrations using DbFlow

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 –

  1. 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.
  2. 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.

  1. Use @Migration annotation in DbFlow library and specify the new database version (by incrementing the existing version) and the database class.
  2. Extend BaseMigration and override migrate method.
  3. 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.
  4. Create an array of java classes which are created/modified.
  5. We wrap the SQL query inside a Database Wrapper class which prevents it from running recursively.
  6. 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};

for (Class<?> recreate: recreated) {
ModelAdapter modelAdapter = FlowManager.getModelAdapter(recreate);
databaseWrapper.execSQL(DROP_TABLE + modelAdapter.getTableName());
databaseWrapper.execSQL(modelAdapter.getCreationQuery());
}
}
}

Similarly, we can write migration for changing a column of table(s).

Continue Reading

Testing the ViewModels in Open Event Organizer App

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;

compositeDisposable.add(orderRepository.getOrders(id, reload)
.compose(dispose(compositeDisposable))
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.toList()
.subscribe(ordersLiveData::setValue,
throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));

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.

  1. Use Mockito.when to return Observables one by one from ORDERS_LIST whenever the method getOrders of the mock orderRepository is called.
  2. We will use Mockito.InOrder and pass orders, orderRepository and progress to check if they are called in a particular order.
  3. We will use .observeForever method to observe on LiveData objects and add a ArrayList on change.
  4. 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));

InOrder inOrder = Mockito.inOrder(orders, orderRepository, progress);

ordersViewModel.getProgress().observeForever(progress);

orders.onChanged(new ArrayList<>());

ordersViewModel.getOrders(EVENT_ID, false);

inOrder.verify(orders).onChanged(new ArrayList<>());
inOrder.verify(orderRepository).getOrders(EVENT_ID, false);
inOrder.verify(progress).onChanged(true);
inOrder.verify(progress).onChanged(false);
}

Similar approach can be followed for writing tests to check other behaviour of the ViewModel.

References

  1. Official Documentation for testing. https://developer.android.com/reference/android/arch/core/executor/testing/InstantTaskExecutorRule
  2. Official Documentation for JUnit.  https://junit.org/junit4/
  3. Official documentation for Mockito.  http://site.mockito.org/
  4. Open Event Organizer App codebase.  https://github.com/fossasia/open-event-orga-app
Continue Reading

Swipe to Check In/Out in Open Event Organizer App

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.

public SwipeController(OrderDetailViewModel orderDetailViewModel, OrderAttendeesAdapter orderAttendeesAdapter, Context context) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.orderDetailViewModel = orderDetailViewModel;
this.orderAttendeesAdapter = orderAttendeesAdapter;

closeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.close);
doneIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.done);

paintGreen.setColor(context.getResources().getColor(R.color.light_green_500));
paintRed.setColor(context.getResources().getColor(R.color.red_500));
}

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;

If (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition()) == null)
makeMovementFlags(dragFlags, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);

if (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition())) {
return makeMovementFlags(dragFlags, ItemTouchHelper.LEFT);
} else {
return makeMovementFlags(dragFlags, ItemTouchHelper.RIGHT);
}
}

The onChildDraw method involves the code doing actual drawing. The variables used in code are discussed below.

  1. ActionState – Checks the state of the recycler view item. We proceed with the below logic if the item is being swiped.
  2. dX – The distance by which the item is swiped. Positive for left and negative for right.
  3. Background – Background of the viewholder. Rectangular in shape and dimensions changed with change in dX.
  4. IconDest – Calculates the position where the icons (close icon or done icon) is placed in canvas
  5. Canvas – Java Canvas on which the drawing is done. We set the background and draw the bitmaps on their location in canvas.
@Override
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() – (float) itemView.getTop();
float width = height / 3;
RectF background;
Paint paint;
Bitmap icon;
RectF iconDest;

if (dX > 0) {
background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom());
paint = paintGreen;
icon = doneIcon;
iconDest = new RectF((float) itemView.getLeft() + width,
(float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width,
(float) itemView.getBottom() – width);
} else {
background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom());
paint = paintRed;
icon = closeIcon;
iconDest = new RectF((float) itemView.getRight() – 2 * width,
(float) itemView.getTop() + width, (float) itemView.getRight() – width,
(float) itemView.getBottom() – width);
}

canvas.drawRect(background, paint);
canvas.drawBitmap(icon, null, iconDest, paint);
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

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();

orderDetailViewModel.toggleCheckIn(position);
orderAttendeesAdapter.notifyItemChanged(position);
}

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.

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}

Resources

  1. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
  2. Official documentation for ItemTouchHelper.SimpleCallback https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback
Continue Reading

Showing Order Details in Eventyay Organizer Android App

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.

@GET(“orders/{identifier}?include=event”)
Observable<Order> getOrder(@Path(“identifier”) String identifier);

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.

@NonNull
@Override
public Observable<Order> getOrder(String orderIdentifier, boolean reload) {
Observable<Order> diskObservable = Observable.defer(() ->
repository
.getItems(Order.class, Order_Table.identifier.eq(orderIdentifier)).take(1)
);

Observable<Order> networkObservable = Observable.defer(() ->
orderApi.getOrder(orderIdentifier)
.doOnNext(order -> repository
.save(Order.class, order)
.subscribe()));

return repository
.observableOf(Order.class)
.reload(reload)
.withDiskObservable(diskObservable)
.withNetworkObservable(networkObservable)
.build();
}

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 {

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.order_detail_fragment, container, false);

orderDetailViewModel = ViewModelProviders.of(this, viewModelFactory).get(OrderDetailViewModel.class);

return binding.getRoot();
}

@Override
public void onStart() {
super.onStart();
setupRefreshListener();

orderDetailViewModel.getOrder(orderIdentifier, eventId, false).observe(this, this::showOrderDetails);
orderDetailViewModel.getProgress().observe(this, this::showProgress);
orderDetailViewModel.getError().observe(this, this::showError);
}
}

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;

compositeDisposable.add(orderRepository.getOrder(identifier, reload)
.compose(dispose(compositeDisposable))
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(order -> orderLiveData.setValue(order),
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));

if (!reload) {
getEvent(eventId);
}

return orderLiveData;
}
}

References

  1. Codebase for Open Event Orga App https://github.com/fossasia/open-event-orga-app
  2. Official documentation for LiveData Architecture Component https://developer.android.com/topic/libraries/architecture/livedata
  3. Official Github Repository of Retrofit  https://github.com/square/retrofit
  4. Official Github Repository for RxJava https://github.com/ReactiveX/RxJava
Continue Reading

Share Events in the Open Event Organizer Android App

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.

  1. The Event Name and Date are shown in the CardView.
  2. User can copy Event External URL by clicking on Copy URL option.
  3. 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.
  4. 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.

@Override
public Observable<Event> getEvent(long eventId, boolean reload) {
Observable<Event> diskObservable = Observable.defer(() ->
repository
.getItems(Event.class, Event_Table.id.eq(eventId))
.filter(Event::isComplete)
.take(1)
);

Observable<Event> networkObservable = Observable.defer(() ->
eventApi
.getEvent(eventId)
.doOnNext(this::saveEvent));

return repository.observableOf(Event.class)
.reload(reload)
.withDiskObservable(diskObservable)
.withNetworkObservable(networkObservable)
.build();
}

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 {

protected LiveData<Event> getEvent(long eventId, boolean reload) {
if (eventLiveData.getValue() != null && !reload)
return eventLiveData;

compositeDisposable.add(eventRepository.getEvent(eventId, reload)
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(event -> {
this.event = event;
eventLiveData.setValue(event);
},
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));

return eventLiveData;
}

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.

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Official documentation for ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel
  4. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
Continue Reading

Change Password Feature for Open Event Android Organizer App

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);

AppCompatActivity activity = ((AppCompatActivity) getActivity());
activity.setSupportActionBar(binding.toolbar);

ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}

return binding.getRoot();
}

@Override
public void onStart() {
super.onStart();
getPresenter().attach(this);
binding.setOrganizerPassword(getPresenter().getChangePasswordObject());
getPresenter().start();

binding.btnChangePassword.setOnClickListener(view -> {
if (!validator.validate())
return;

String url = binding.url.baseUrl.getText().toString().trim();
getPresenter().setBaseUrl(url, binding.url.overrideUrl.isChecked());
getPresenter().changePasswordRequest(binding.oldPassword.getText().toString(),
binding.newPassword.getText().toString(),
binding.confirmNewPassword.getText().toString());

});
}

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;
}

organizerPasswordObject.setOldPassword(oldPassword);
organizerPasswordObject.setNewPassword(newPassword);
organizerPasswordObject.setConfirmNewPassword(confirmPassword);

changePasswordModel.changePassword(organizerPasswordObject)
.compose(disposeCompletable(getDisposable()))
.compose(progressiveErroneousCompletable(getView()))
.subscribe(() -> getView().onSuccess(“Password Changed Successfully”), Logger::logError);
}
}

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.

@POST(“auth/change-password”)
Observable<ChangePasswordResponse> changePassword(@Body Map<String, ChangePassword> changePassword);

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.

References

  1. Official documentation for RxJava by ReactiveX https://github.com/ReactiveX/RxJava
  2. Official documentation for Retrofit by Square Inc https://github.com/square/retrofit
  3. Codebase for Open Event Organizer App on Github https://github.com/fossasia/open-event-orga-app
  4. Open Event Server deployment at heroku https://open-event-api-dev.herokuapp.com/
Continue Reading

Create Session in Open Event Android Organizer Application

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.

Open Event Android Organizer Application is a client for Open Event Server which provides the REST API.

Problem

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

container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater,

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();

track.setId(trackId);
event.setId(eventId);
session.setTrack(track);
session.setEvent(event);

sessionRepository
.createSession(session)
.compose(dispose(getDisposable()))
.compose(progressiveErroneous(getView()))
.subscribe(createdSession ->

getView().onSuccess(“Session Created”), Logger::logError);
}
}

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.

@Override
public Observable<Session> createSession(Session session) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return sessionApi
.postSession(session)
.doOnNext(created -> {
created.setTrack(session.getTrack());
created.setEvent(session.getEvent());
repository
.save(Session.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

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.

References

  1. Official RxJava Project on Github by ReactiveX https://github.com/ReactiveX/RxJava.
  2. Official Retrofit Project on Github by Square Inc https://github.com/square/retrofit.
  3. Official Open Event Organizer App on Github by FOSSASIA https://github.com/fossasia/open-event-orga-app.
  4. Documentation for REST API of Open Event Server on Heroku by FOSSASIA https://open-event-api-dev.herokuapp.com/.
Continue Reading

Creating Activity for Visualizing Recorded Sensor Data from List Items

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:

Figure 1 shows the layout of the Activity as produced in Android editor

The layout contains three views:-

  1. 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.
  2. In the middle, there is a Card view containing two rows which will simply show the date and time of recording.
  3. 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:-

  1. First I stored the position of the item clicked inside a variable.

    int positionVar = holder.getAdapterPosition();
  2. 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();
  3. 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.
    Intent intent = new Intent(context, SensorGraphViewActivity.class);
    intent.putExtra(SensorGraphViewActivity.TYPE_SENSOR, item.getSensor());
    intent.putExtra(SensorGraphViewActivity.DATA_FOREIGN_KEY, item.getUniqueRef());
    intent.putExtra(SensorGraphViewActivity.DATE_TIME_START,item.getDateTimeStart());
    intent.putExtra(SensorGraphViewActivity.DATE_TIME_END,item.getDateTimeEnd());
    intent.putExtra(SensorGraphViewActivity.TIME_ZONE,item.getTimeZone());
    intent.putExtra(SensorGraphViewActivity.LATITUDE,item.getLatitude());
    intent.putExtra(SensorGraphViewActivity.LONGITUDE,item.getLongitude());
    
    context.startActivity(intent);
    

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.

Figure 2 shows the layout of the activity after being populated with data.

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.

Resources

  1. Storing Recorded Sensor Data in Realm Database – My blog where I created the Realm Model classes to store recorded data.
  2. Using RealmRecyclerView Adapter to Show Recorded Sensor Experiments – My previous blog where I created the RecyclerView.
  3. Saving Sensor Data in CSV format – Blog by Padmal storing the data in CSV format.
Continue Reading

How to pass data between fragments of an Activity in Android app

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
public static void setParameters(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.
@Override
    public void onDestroyView() {
        super.onDestroyView();
        highValue = getValueFromText(highLimit, 0, highLimitMax);
        updatePeriodValue = getValueFromText(updatePeriod, updatePeriodMin, updatePeriodMax + 100);
        Fragment1.setParameters(selectedSensor, highValue, updatePeriodValue);
    }

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.

Resources

  1. Blog on how to pass data between fragments of different/same activities: https://www.journaldev.com/14207/android-passing-data-between-fragments
Continue Reading

Prevent Android Activity from Operating while using Bottom Sheet in PSLab App

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.
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                              if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED)
                    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
tvShadow.setVisibility(View.GONE);
      }
});
  • Now, override the OnSlide() method of the GestureDetector class and add the following code to it.
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
    Float value = (float) MathUtils.map((double) slideOffset, 0.0, 1.0, 0.0, 0.8);
    view.setVisibility(View.VISIBLE);
    view.setAlpha(value);
   }

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

Resources

  1. http://thetechnocafe.com/make-bottom-sheet-android/: Blog on how to make a Bottom Sheet in Android
Continue Reading
Close Menu