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

Create Call for Speakers with Open Event Organizer Android App

In The Open Event Organizer Android app, we were providing variety of features to the user but the functionality to create call for speakers for an Event was missing. This feature was required to attract speakers from all over the world to participate in the event and make it a success. Theis blog explains how we added this feature to the project us following MVVM Architecture and using libraries like Retrofit, RxJava, Db Flow etc.

Objective

The goal will be to provide an option to the organizer to create Call for Speakers for an Event (if it doesn’t exist already). We will provide the organizer a Floating Action Button in Speakers Call detail layout which will open a fragment which contain all the relevant fields provided by Open Event Server. The organizer can fill up all the fields as per requirement and create the call for speakers.

Specifications

Let’s move on to the implementation details.

First, we will create the model which will contain all the fields offered by server. We are using Lombok library to reduce boilerplate code using annotations. For example, @Data annotation is used to generate getters and setters. @NoArgsConstructor to generate no arguments constructor as the name suggests. Along with that we are using @JonNaming annotation provided by Jackson library to serialize the model using KebabCaseStrategy. Also, the Table annotation provided by Raziz DbFlow library to make a table in database for this model.

@Data
@Type(“speakers-call”)
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@Table(database = OrgaDatabase.class, allFields = true)
public class SpeakersCall {@Id(LongIdHandler.class)
@PrimaryKey
public Long id;@Relationship(“event”)
@ForeignKey(onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String announcement;
public String hash;
public String privacy;
public String startsAt;
public String endsAt;
}

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Also we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVVM architecture.

We will make a POST request to the server . So we specify the declaration in SpeakersCallApi.java

@POST(“speakers-calls”)
Observable<SpeakersCall> postSpeakersCall(@Body SpeakersCall speakersCall);

We will use the method createSpeakersCall(SpeakersCall speakersCall) in SpeakersCallRepositoryImpl.java to interact with the SpeakersCallApi and make the network request for us. The response will be passed on to the SpeakersCallViewModel method which calls it.

@Override
public Observable<SpeakersCall> createSpeakersCall(SpeakersCall speakersCall) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return speakersCallApi
.postSpeakersCall(speakersCall)
.doOnNext(created -> {
created.setEvent(speakersCall.getEvent());
repository
.save(SpeakersCall.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

Next, we will see the code for SpeakersCallViewModel which is using MVVM ViewModel to handle the processing logic for SpeakesCallFragment. Here we are passing SpeakersCallRepository instance in constructor. Methods like getProgress, getError and getSuccess pass on a LiveData Object from ViewModel to Fragment and the latter observes the changes on the object and show these in UI. CreateSpeakersCall method is used to call speakersCallRepository to send a POST request to server asynchronously using Retrofit and RxJava. Further, initialize method is used to set initial values of time and date to SpeakersCall object.

public class CreateSpeakersCallViewModel extends ViewModel {
public void createSpeakersCall(long eventId) {
if (!verify())
return;Event event = new Event();event.setId(eventId);
speakersCallLive.getValue().setEvent(event);

compositeDisposable.add(speakersCallRepository.createSpeakersCall(speakersCallLive.getValue())
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(var -> success.setValue(“Speakers Call Created Successfully”),
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));
}
}

We will create a Fragment class to bind the UI elements to the Model class so that they can be processed by ViewModel. The creation form contains less fields therefore BaseBottomSheetFragment is used. In the onStart method, we are binding several LiveData objects like SpeakersCall, Progress, Error, Success to the UI and then observe changes in them. Using LiveData we don’t have to write extra code to handle screen rotation and to update UI when the binded object is modified.

public class CreateSpeakersCallFragment extends BaseBottomSheetFragment implements CreateSpeakersCallView {
@Override
public void onStart() {
super.onStart();
createSpeakersCallViewModel.getSpeakersCall().observe(this, this::showSpeakersCall);
createSpeakersCallViewModel.getProgress().observe(this, this::showProgress);
createSpeakersCallViewModel.getError().observe(this, this::showError);
createSpeakersCallViewModel.getSuccess().observe(this, this::onSuccess);
createSpeakersCallViewModel.initialize();
}
}

We will design the speaker create form using Two Way Data Binding to bind SpeakersCall object to the UI and use TextInputEditText to take user Input. Along with that we have used Time and Date pickers to make it easier to select Date and Time. For viewing the complete code, please refer here.

Thus, we are able to build a responsive, reactive UI using Android Architectural Components.

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 Orga App https://github.com/fossasia/open-event-orga-app
Continue Reading

Retrofit2 Rxjava2 Error Response Handling in Open Event Organizer App

In the Open Event Organizer Android app the challenge is to provide user, a readable error description for the input requests. The Organizer App was showing a coded message which was understandable only to a programmer making it unfriendly to the common user. The blog describes how we tackled this problem and implemented a mechanism to provide user friendly error messages for user requests (if any).

Let’s consider the scenario when an organizer want to create an Event or maybe a Ticket so what he has to do is fill out a form containing some user input fields and click on submit button to send the data to the server which in turn sends a response for the request. If the information is valid the server sends a HTTP 201 Response and user gets feedback that the Event/Ticket is created. But if the information is not valid the user gets feedback that there is HTTP 422 error in your request. There is no readable description of error provided to the user.

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Further, we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVP architecture. Let’s move on to the code.

Retrofit to the rescue

Now we will see how we can extract the details about the error response and provide user a better feedback rather than just throwing the error code.

Firstly let’s make the Request to our REST API Server using Retrofit2.

@POST(“faqs”)
Observable<Faq> postFaq(@Body Faq faq);

Here we are making a POST request and expecting a Observable<Faq> Response from the server.The  @Body annotation indicates the Request Body is an Faq object.

Please note that Observable used here is ReactiveX Observable so don’t confuse it with java.util Observable.

public class Faq {
@Id(LongIdHandler.class)
public Long id;  @Relationship(“event”)
@ForeignKey(stubbedRelationship = true,

onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String question;
public String answer;
}

Let’s say the API declares both question and answer as mandatory fields  for creation of an Faq. We supply the following input to the app.

question = “Do I need to buy a Ticket to get In ?”;
answer = null

We used RxJava to make an asynchronous request to server. In case of error we will get a Retrofit throwable and we will pass that on to ErrorUtils.java which will do all the processing and return a readable error message.

faqRepository
.createFaq(faq)
.compose(dispose(getDisposable()))
.compose(progressive(getView()))  .doOnError(throwable ->

getView().showError(ErrorUtils.getMessage(throwable)))
.subscribe(createdFaq -> {
getView().onSuccess(“Faq Created”);
getView().dismiss();
}, Logger::logError);

Now we will extract the ResponseBody from the Retrofit throwable.

ResponseBody responseBody = ((HttpException) throwable)

.response().errorBody();
return getErrorDetails(responseBody);

The ResponseBody is a JSON containing all the information about the error.

{
“errors”: [
{
“status”: “422”,
“source”: {
“pointer”: “/data/attributes/answer”
},
“detail”: “Missing data for required field.”,
“title”: “Validation error”
}
],
“jsonapi”: {
“version”: “1.0”
}
}

In order to provide better feedback to user regarding the error we have to parse the response JSON and extract the pointed field (which in this case is – “answer”) and then combine it with the value corresponding to the “detail” key. Following is the relevant section from ErrorUtils.java (for viewing the complete ErrorUtils.java please refer here).

public static String getErrorDetails(ResponseBody responseBody) {
try {
JSONObject jsonObject = new JSONObject(responseBody.string());
JSONObject jsonArray = new    JSONObject(jsonObject.getJSONArray(“errors”).get(0).toString());
JSONObject errorSource =

new JSONObject(jsonArray.get(“source”).toString());

try {
String pointedField =

getPointedField(errorSource.getString(“pointer”));
if (pointedField == null)
return jsonArray.get(“detail”).toString();
else
return jsonArray.get(“detail”).toString()

.replace(“.”, “”) + “: ” + pointedField;
} catch (Exception e) {
return jsonArray.get(“detail”).toString();
}

} catch (Exception e) {
return null;
}
}

public static String getPointedField(String pointerString) {
if (pointerString == null || Utils.isEmpty(pointerString))
return null;
else {
String[] path = pointerString.split(“/”);
if (path.length > 3)
return path[path.length – 1];
else
return null;
}
}

The final error message returned by ErrorUtils in case of HTTP 422 –

Missing data for required field: answer

Similar approach can be followed for extracting the error “title” or “status” .

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. Codebase for Open Event Organizer App on Github https://github.com/fossasia/open-event-orga-app
Continue Reading
  • 1
  • 2
Close Menu
%d bloggers like this: