Updating User information in Open Event Android

A user can update its information such as first name, last name from the Edit Profile Fragment in Open Event Android. Edit Profile Fragment can be accessed from the menu inside the Profile page. On opening Edit Profile Fragment user can interact with the simple UI to update his/her information. This blog post will guide you on how its implemented in Open Event Android. To update a User we send a patch request to Open Event Server. The patch request contains the Updated User as body and auth token as header and it returns the updated user in response. Following it what the interface method looks like @PATCH("users/{id}") fun updateUser(@Body user: User, @Path("id") id: Long): Single<User> This method is exposed to the View Model using a service layer function which calls the above function and also inserts the returned user in the database. fun updateUser(user: User, id: Long): Single<User> {        return authApi.updateUser(user, id).map {            userDao.insertUser(it)            it        }    } On using map on Single<User> returned by updateUser we can access the user inside the scope which is then inserted into the database using the DAO method insert user and the same user object is returned by the function. This service layer method is then used in updateUser method of EditProfileViewModel class which specifies how it is subscribed and on which thread observer should be set etc. The Edit Profile Fragment fragment calls this method whenever the user clicks on the Update button. fun updateUser(firstName: String, lastName: String) {        val id = authHolder.getId()        if (firstName.isEmpty() || lastName.isEmpty()) {            message.value = "Please provide first name and last name!"            return        }        compositeDisposable.add(authService.updateUser(User(id = id,          firstName = firstName, lastName = lastName), id)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .doOnSubscribe {                    progress.value = true                }                .doFinally {                    progress.value = false                }                .subscribe({                    message.value = "User updated successfully!"                    Timber.d("User updated")                }) {                    message.value = "Error updating user!"                    Timber.e(it, "Error updating user!")                })    } UpdateUser takes two parameters first name and last name if any of these parameters is empty the function returns with an error message which is displayed on the UI else service layer update user function is called with argument a User object with first name and last name as provided to view model function and an id which is accessed using authHolder’s getId method. Whenever this is subscribed we set progress.value true which displays spinner on the UI this is set false after the operation is complete. If the patch request results in success then toast message is shown on screen and a success message is logged similar to this in case of error, an error toast is displayed and an error is logged. This goes for the logic to update user we also need UI and menu item which launches this fragment. Inside Menu.xml add the following snippet of code <item   android:id="@+id/edit_profile"   android:title="@string/edit_profile" /> This will create a menu item inside the ProfileFragment. The next step is to wire this logic which tells what happens when the user selects this menu item. The following code wires it…

Continue ReadingUpdating User information in Open Event Android

Creating Orders in Open Event Android

An Order is generated whenever a user buys a ticket in Open Event Android. It contains all the details regarding the tickets and their quantity, also information regarding payment method and relation to list of attendees, through this blog post we will see how Orders are generated in Open Event Android. Implementing Order system can be divided into following parts Writing model class to serialize/deserialize API responses Creating TypeConverter for Object used in Model class Creating the API interface method Wiring everything together Model Class Model class server two purpose - Entity class for storing orders in room Serialize / Deserialize API response The architecture of the Order Model Class depends upon the response returned by the API, different fields inside the Entity Class defines what different attributes an Order consists of and their data types. Since every Order has a relationship with Event and Attendee we also have to define foreign key relations with them. Given below is the implementation of the Order Class in Open Event Android. @Type("order") @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) @Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = ["id"], childColumns = ["event"], onDelete = ForeignKey.CASCADE)), (ForeignKey(entity = Attendee::class, parentColumns = ["id"], childColumns = ["attendees"], onDelete = ForeignKey.CASCADE))]) data class Order(        @Id(IntegerIdHandler::class)        @PrimaryKey        @NonNull        val id: Long,        val paymentMode: String? = null,        val country: String? = null,        val status: String? = null,        val amount: Float? = null,        val orderNotes: String? = null,        @ColumnInfo(index = true)        @Relationship("event")        var event: EventId? = null,        @Relationship("attendees")        var attendees: List<AttendeeId>? = null )   We are using Jackson for serializing/deserializing JSON response, @Type(“order”) annotation tells jackson that the following object is for key order in json response. Since we are using this as our room entity class we will also have to add a @Entity annotation to this class. Order contains attendee and event id fields which are foreign keys to other entity classes, this also has to be explicitly mentioned while writing the @Entitty annotation as shown in the snippet above . All relationship must be annotated with @Relationship annotation. All the variables serves as attributes in the order table and key name for json conversions. The fields of this class are the attributes for the Order Table. Payment mode, country, status are all made up of primitive data type and hence require no type convertors whereas we will have to specify type converter for objects like eventId and List<Attendees> Type Converter Type Converter allows us to store any custom object type inside room database. Essentially we break down the Object into smaller primitive data types that Object is composed of and which can be stored by room database. To create a TypeConverter we have to add a @TypeConverter annotation to it, this tells room that this is a special function. For every custom Object, you have to create two different TypeConverter functions. One takes the Object and converts it into primitive data type and the other takes the primitive data type and constructs your custom Object from it. For the Order data class we discussed…

Continue ReadingCreating Orders in Open Event Android

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…

Continue ReadingRetrofit2 Rxjava2 Error Response Handling in Open Event Organizer App

Implementing Skill Detail Section in SUSI Android App

SUSI Skills are rules that are defined in SUSI Skill Data repo which are basically the responses SUSI gives to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which further fetches response from SUSI Skill Data and gives the response to the app. Similarly, when we need to list all skills, an API call is made to server to list all skills. The server then checks the SUSI Skill Data repo for the skills and then return all the required information to the app. Then the app displays all the information about the skill to user. User then can view details of each skill and then interact on the chat interface to use that skill. This process is similar to what SUSI Skill CMS does. The CMS is a skill wiki like interface to view all skills and then edit them. Though the app can not be currently used to edit the skills but it can be used to view them and try them on the chat interface. API Information For listing SUSI Skill groups, we have to call on /cms/getGroups.json This will give you all groups in SUSI model in which skills are present. Current response: { "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }}, "accepted": true, "groups": [ "Small Talk", "Entertainment", "Problem Solving", "Knowledge", "Assistants", "Shopping" ], "message": "Success: Fetched group list" } So, the groups object gives all the groups in which SUSI Skills are located. Next comes, fetching of skills. For that the endpoint is /cms/getGroups.json?group=GROUP_NAME Since we want all skills to be fetched, we call this api for every group. So, for example we will be calling http://api.susi.ai/cms/getSkillList.json?group=Entertainment for getting all skills in group “Entertainment”. Similarly for other groups as well. Sample response of skill: { "accepted": true, "model": "general", "group": "Shopping", "language": "en", "skills": {"amazon_shopping": { "image": "images/amazon_shopping.png", "author_url": "https://github.com/meriki", "examples": ["Buy a dress"], "developer_privacy_policy": null, "author": "Y S Ramya", "skill_name": "Shop At Amazon", "dynamic_content": true, "terms_of_use": null, "descriptions": "Searches items on Amazon.com for shopping", "skill_rating": null }}, "message": "Success: Fetched skill list", "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }} } It gives all details about skills: image author_url examples developer_privacy_policy author skill_name dynamic_content terms_of_use descriptions skill_rating Implementation in SUSI Android App Skill Detail Section UI of Google Assistant Skill Detail Section UI of SUSI SKill CMS Skill Detail Section UI of SUSI Android App The UI of skill detail section in SUSI Android App is the mixture of UI of Skill detail section in Google Assistant ap and SUSI Skill CMS. It displays details of skills in a beautiful manner with horizontal recyclerview used to display the examples. So, we have to display following details about the skill in Skill Detail Section: Skill Name Author Name Skill Image Try it Button Description Examples Rating Content type (Dynamic/Static) Terms of Use Developer’s Privacy policy Let’s see the implementation. 1. Whenever a skill Card View is clicked, showSkillDetailFragment()…

Continue ReadingImplementing Skill Detail Section in SUSI Android App

Implementing Change Password Feature in SUSI Android App using Custom Dialogs

Recently a new servlet was implemented on the SUSI Server about changing the password of the logged in user. This feature comes in handy to avoid unauthorized usage of the SUSI Account. Almost all the online platforms have this feature to change the password to avoid notorious user to unethical use someone else’s account. In SUSI Android app this new API was used with a nice UI to change the password of the user. The process is very simple and easy to grasp. This blog will try to cover the API information and implementation of the Change Password feature in the android client. API Information For changing the password of SUSI Account of the user, we have to call on  /aaa/changepassword.json We have to provide three parameters along with this api call: changepassword:  Email of user (type string) using which user is logged in. password:  Old password (type string with min length of 6) of the user. newpassword: New password (type string with min length of 6) of the user. access_token: An encrypted access_token indicating user is logged in. Sample Response (Success) { "session": {"identity": { "type": "email", "name": "YOUR_EMAIL_ADDRESS", "anonymous": false }}, "accepted": true, "message": "Your password has been changed!" } Error Response (Failure). This happens when user is not logged in: HTTP ERROR 401 Problem accessing /aaa/changepassword.json. Reason: Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous' Implementation in SUSI Android App The change password option is located in Settings Activity and displayed only when user is logged in. So, if a logged in user wants to change the password of his/her SUSI AI account, he/she can simply go to the Settings and click on the option. Clicking on the options open up a dialog box with 3 input layouts for: Current Password New Password Confirm New Password So, user can simply add these three inputs and click “Ok”. This will change the password of their account. Let’s see some code explanation. When user clicks on the “reset password” option from the settings, the showResetPasswordAlert() method is called which displays the dialog. And when user clicks on the “OK” button the resetPassword method() in the presenter is called passing input from the three input layout as parameters. settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString()) fun showResetPasswordAlert() { val builder = AlertDialog.Builder(activity) val resetPasswordView = activity.layoutInflater.inflate(R.layout.alert_reset_password, null) password = resetPasswordView.findViewById(R.id.password) as TextInputLayout newPassword = resetPasswordView.findViewById(R.id.newpassword) as TextInputLayout conPassword = resetPasswordView.findViewById(R.id.confirmpassword) as TextInputLayout builder.setView(resetPasswordView) builder.setTitle(Constant.CHANGE_PASSWORD) .setCancelable(false) .setNegativeButton(Constant.CANCEL, null) .setPositiveButton(getString(R.string.ok), null) resetPasswordAlert = builder.create() resetPasswordAlert.show() setupPasswordWatcher() resetPasswordAlert.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener { settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString()) } } In the resetPassword method, all details about the passwords are checked like: If passwords are not empty. If passwords’ lengths are greater than 6. If new password and confirmation new password matches     When all the conditions are satisfied and all the inputs are valid, resetPassword() in model is called which makes network call to change password of the user. settingModel.resetPassword(password,newPassword,this) override fun resetPassword(password: String, newPassword: String, conPassword: String) { if (password.isEmpty()) { settingView?.invalidCredentials(true,…

Continue ReadingImplementing Change Password Feature in SUSI Android App using Custom Dialogs

Implementing Skill Listing in SUSI Android App using Nested RecyclerViews

SUSI Skills are rules that are defined in SUSI Skill Data repo which are basically the responses SUSI gives to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which further fetches response from SUSI Skill Data and gives the response to the app. Similarly, when we need to list all skills, an API call is made to server to list all skills. The server then checks the SUSI Skill Data repo for the skills and then return all the required information to the app. Then the app displays all the information about the skill to user. User then can view details of each skill and then interact on the chat interface to use that skill. This process is similar to what SUSI Skill CMS does. The CMS is a skill wiki like interface to view all skills and then edit them. Though the app can not be currently used to edit the skills but it can be used to view them and try them on the chat interface. API Information For listing SUSI Skill groups, we have to call on  /cms/getGroups.json This will give you all groups in SUSI model in which skills are present. Current response: { "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }}, "accepted": true, "groups": [ "Small Talk", "Entertainment", "Problem Solving", "Knowledge", "Assistants", "Shopping" ], "message": "Success: Fetched group list" } So, the groups object gives all the groups in which SUSI Skills are located. Next comes, fetching of skills. For that the endpoint is /cms/getGroups.json?group=GROUP_NAME Since we want all skills to be fetched, we call this api for every group. So, for example we will be calling http://api.susi.ai/cms/getSkillList.json?group=Entertainment for getting all skills in group “Entertainment”. Similarly for other groups as well. Sample response of skill: { "accepted": true, "model": "general", "group": "Shopping", "language": "en", "skills": {"amazon_shopping": { "image": "images/amazon_shopping.png", "author_url": "https://github.com/meriki", "examples": ["Buy a dress"], "developer_privacy_policy": null, "author": "Y S Ramya", "skill_name": "Shop At Amazon", "dynamic_content": true, "terms_of_use": null, "descriptions": "Searches items on Amazon.com for shopping", "skill_rating": null }}, "message": "Success: Fetched skill list", "session": {"identity": { "type": "host", "name": "14.139.194.24", "anonymous": true }} } It gives all details about skills: image author_url examples developer_privacy_policy author skill_name dynamic_content terms_of_use descriptions skill_rating Implementation in SUSI Android App Skill Listing UI of Google Assistant Skill Listing UI of SUSI SKill CMS Skill Listing UI of SUSI Android App The UI of skill listing in SUSI Android App is the mixture of UI of Skill listing in Google Assistant ap and SUSI Skill CMS. It displays skills in a beautiful manner with horizontal recyclerview nested in vertical recyclerview. So, for implementing horizontal recyclerview inside vertical recyclerview, you need two viewholders and two adapters (one each for a recyclerview). Let’s see the implementation. 1. First task is to fetch the information of groups in which skills are located. This line calls method in SkillListModel which then makes an API call to fetch groups. skillListingModel.fetchGroups(this) 2.…

Continue ReadingImplementing Skill Listing in SUSI Android App using Nested RecyclerViews

Implementing Feedback Feature in SUSI Android App

Recently, on SUSI Server, a new servlet was added which is used to rate SUSI Skills either positive or negative. The server stores the rating of a particular skill in a JSON file. These ratings help in improving answers provided by SUSI. So, the server part is done and it was required to implement this in the SUSI Android App. In this blog, I will cover the topic of implementation of the Rating or Feedback feature in SUSI Android App. This will including all the cases when feedback should be sent, when it should not be sent, when to send positive feedback, when to send negative feedback, etc. API Information For rating a SUSI Skill, we have to call on  /cms/rateSkill.json providing 5 parameters which are: model: The model of SUSI Skill. (String) group: The Group under the model in which that particular skill resides. (String) language: The language of skill. (String) skill: This is skill name. (String) rating: This can be two strings, either “positive” or “negative”. String) Basically, in the SUSI Skill Data repo (in which all the skills are stored), models, groups language etc are part of folder structure. So, if a skill is located here https://github.com/fossasia/susi_skill_data/blob/master/models/general/Knowledge/en/news.txt This would mean model = general group = Knowledge language = en skill = news rating = positive/negative Implementation in SUSI Android App      So, when the like button on a particular skill is clicked, a positive call is made and when the dislike button is clicked, a negative call is made. Let’s see example when the thumbs up button or like button is clicked. There can be three cases possible: None of Like button or dislike button is clicked already: In this case, initially, both like and dislike button will be transparent/hollow. So, when like button is clicked, the like button will be colored blue and a call will be made with positive feedback. Like button is already clicked: In this case, like button is already clicked. So, it will already be blue. So, when user clicks again on positive button, it should get back to normal/hollow indicating rating which was sent is cancelled and a a call will be made with negative feedback thus cancelling or neutralizing the earlier, positive feedback. Dislike button is already clicked: In this case, the dislike button is already blue, indicating a negative call is already made. So, now when the like button is clicked, we need to cancel the earlier negative feedback call and sending another negative feedback call. Thus, sending two negative feedback calls. And after that coloring dislike button as blue. Look at the code below. It is self explanatory. There are three if-else conditions covering all the above mentioned three cases. thumbsUp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { thumbsUp.setImageResource(R.drawable.thumbs_up_solid); if(!model.isPositiveRated() && !model.isNegativeRated()) { rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context); setRating(true, true); } else if(!model.isPositiveRated() && model.isNegativeRated()) { setRating(false, false); thumbsDown.setImageResource(R.drawable.thumbs_down_outline); rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context); sleep(500); rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context); setRating(true, true); } else if (model.isPositiveRated() && !model.isNegativeRated()) { rateSusiSkill(Constant.NEGATIVE, model.getSkillLocation(), context);…

Continue ReadingImplementing Feedback Feature in SUSI Android App

Dynamic Base URL Support in the Open Event Organizer App

Open Event API Server acts as a backend for Open Event Organizer Android App. The server has a development instance running on the web for developers. Developers use this instance to try out new feature additions, bug fixings and other such changes in the source code. And when confirmed working, these changes are updated to the main running instance which is kept live throughout for the users. Similarly for Android app developers, to test the app with both the instances, we have implemented the dynamic base URL support in the app. The app has a default base URL set to development instance or main instance dependent on the debug mode. That means the app will use a server on developer instance when used under debug mode and will use a main instance server if used under release mode. The app also provides an option to enter an alternate URL while login in the app which replaces default base URL in the app for the session. In the organizer app, we are using Retrofit + Okhttp for handling network requests and dagger for dependency injection. The OkhttpClient provider in NetworkModule class looks like: @Provides @Singleton OkHttpClient providesOkHttpClient(HostSelectionInterceptor interceptor) { return new OkHttpClient.Builder() .addNetworkInterceptor(new StethoInterceptor()) .build(); }   Retrofit had a support for mutable base URL in the earlier versions but the feature is no longer available in the recent versions. We are using Interceptor class for changing base URL. The class has a method named intercept, which gets called at each network request. In this method, base URL is reset to the new URL. So first you have to extend Interceptor class and reset base URL in the intercept method. The Interceptor class in the app looks like: public final class HostSelectionInterceptor implements Interceptor { private String host; private String scheme; public HostSelectionInterceptor(){ //Intentionally left blank } public void setInterceptor(String url) { HttpUrl httpUrl = HttpUrl.parse(url); scheme = httpUrl.scheme(); host = httpUrl.host(); } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); // If new Base URL is properly formatted then replace the old one if (scheme != null && host != null) { HttpUrl newUrl = original.url().newBuilder() .scheme(scheme) .host(host) .build(); original = original.newBuilder() .url(newUrl) .build(); } return chain.proceed(original); } }   The class has a private string field host to save base URL. The method setInterceptor is used to change the base URL. Once the base URL is changed, thereafter all the network requests use changed URL to call. So now our interceptor is ready which can be used to support dynamic base URL in the app. This interceptor is added to Okhttp builder using its method addInterceptor. @Provides @Singleton HostSelectionInterceptor providesHostSelectionInterceptor() { return new HostSelectionInterceptor(); } @Provides @Singleton OkHttpClient providesOkHttpClient(HostSelectionInterceptor interceptor) { return new OkHttpClient.Builder() .addInterceptor(interceptor) .addNetworkInterceptor(new StethoInterceptor()) .build(); }   And now you are able to change base URL just by using the setInterceptor method of Interceptor class from anywhere in the app. And by then all the network calls use the updated base URL.…

Continue ReadingDynamic Base URL Support in the Open Event Organizer App

Implement Caching in the Live Feed of Open Event Android App

In the Open Event Android App, a live feed from the event’s Facebook page was recently implemented. Since it was a live feed, it was decided that it was futile to store it in the Realm database of the app. The data of the live feed didn’t persist anywhere, hence the feed used to be empty when the app ran without the internet connection. To solve the problem of data persistence, it was decided to store the feed in the cache. Now, there were two paths before us - use retrofit okhttp cache management or use volley. Since retrofit is used to make the API requests in the app, we used the former. To implement caching with retrofit, its API response should include the cache control header. Since it was not a response generated by a personal server, interceptors were needed to force change the request. Interceptors Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. The solution was to use interceptors to rewrite the calls to force use of cache. Two interceptors were added, application interceptor for the request and the network interceptor for the response. Implementation Create a cache file to store the response. private static Cache provideCache() { Cache cache = null; try { cache = new Cache(new File(OpenEventApp.getAppContext().getCacheDir(), "facebook-feed-cache"), 10 * 1024 * 1024); // 10 MB } catch (Exception e) { Timber.e(e, "Could not create Cache!"); } return cache; }   Create a network interceptor by chaining the response with the cache control header and removing the pragma header to force use of cache. private static Interceptor provideCacheInterceptor() { return chain -> { Response response = chain.proceed(chain.request()); // re-write response header to force use of cache CacheControl cacheControl = new CacheControl.Builder() .maxAge(2, TimeUnit.MINUTES) .build(); return response.newBuilder() .removeHeader("Pragma") .header(CACHE_CONTROL, cacheControl.toString()) .build(); }; }   Create an application interceptor by chaining the request with the cache control header for stale responses and removing the pragma header to make the feed available for offline usage. private static Interceptor provideOfflineCacheInterceptor() { return chain -> { Request request = chain.request(); if (!NetworkUtils.haveNetworkConnection(OpenEventApp.getAppContext())) { CacheControl cacheControl = new CacheControl.Builder() .maxStale(7, TimeUnit.DAYS) .build(); request = request.newBuilder() .removeHeader("Pragma") .cacheControl(cacheControl) .build(); } return chain.proceed(request); }; }   Finally add the cache and the two interceptors while building the okhttp client. OkHttpClient okHttpClient = okHttpClientBuilder.addInterceptor(new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BASIC)) .addInterceptor(provideOfflineCacheInterceptor()) .addNetworkInterceptor(provideCacheInterceptor()) .cache(provideCache()) .build();   Conclusion Working of apps without the internet connection builds up a strong case for corner cases while testing. It is therefore critical to persist data however small to avoid crashes and bad user experience. Resources Complete code reference https://github.com/fossasia/open-event-android/pull/1750 Tutorial on etags in response https://futurestud.io/tutorials/retrofit-2-activate-response-caching-etag-last-modified Tutorial on caching in Retrofit https://caster.io/episodes/retrofit-2-offline-cache/ Retrofit Documentation http://square.github.io/retrofit/ Okhttp Interceptors Documentation https://github.com/square/okhttp/wiki/Interceptors

Continue ReadingImplement Caching in the Live Feed of Open Event Android App

Error Handling in Retrofit 2

For the Open Event android app we were using retofit 1.9 with an okhttp stack plus a gson parser but recently retrofit 2.0 was released and it was a major update in the sense that it a lot of things have been changed. For starters, you don’t have to declare synchronous and asynchronous requests upfront and you can just decide that while executing. The code for that will look something like this. This is how we define our request methods in our api service import retrofit.Call; public interface APIService { @POST(“/list”) Call<Repo> loadRepo(); } Now if we want to make a synchronous request, we can make it like Call<Repo> call = service.loadRepo(); Repo repo = call.execute(); and for an asynchronous request, we can call enqueue() Call<Repo> call = service.loadRepo(); call.enqueue(new Callback<Repo>() { @Override public void onResponse(Response<Repo> response) { // Get result Repo from response.body() } @Override public void onFailure(Throwable t) { } }); And another thing that changed in the async call throws a throwable on failure, so essentially the RetrofitError class is gone and since we were using that in our app, we had to modify the whole error handling in the app, basically from the grounds up. So, when we decided to move to retrofit 2 after the stable version was released, we had to change a lot of code and the main part that was affected was the error handling. So, replacing the retrofitError class, I used the throwable directly to retrieve the error type something like this if (error.getThrowable() instanceof IOException) { errorType = “Timeout”; errorDesc = String.valueOf(error.getThrowable().getCause()); } else if (error.getThrowable() instanceof IllegalStateException) { errorType = “ConversionError”; errorDesc = String.valueOf(error.getThrowable().getCause()); } else { errorType = “Other Error”; errorDesc = String.valueOf(error.getThrowable().getLocalizedMessage()); } This was ofcourse for all failure events. And to handle all response events I compared the HTTP status codes and displayed the errors : Integer statusCode = response.getStatusCode(); if (statusCode.equals(404)) { // Show Errors in a dialog showErrorDialog(“HTTP Error”, statusCode + “Api Not Found”); } This is how we can compare other HTTP errors in retrofit and assign the correct status accordingly. I personally think that this is a better implementation than Retrofit 1.9 and the RetrofitError was a bit tedious to work with. It wasn’t very thought of before implementation because it was not easy to tell what kind of error exactly occured. With Response codes, one can see what are the exact error one faces and can gracefully handle these errors.

Continue ReadingError Handling in Retrofit 2