Implementing Country Preference in Orga App

In the Open Event Orga App, there was no option earlier to save the country preference in the shared preference. So every time the user had to select the country while creating events. Hence an option to select a country was added to the Event Settings. So any value which gets selected here acts as a default country while creation of events.

Steps

  • Add the constant key to the Constants.java class.
public static final String PREF_PAYMENT_COUNTRY = “key”;
  • Create a class CountryPreference.java which extends DialogPreference. It is in this class that all the code related to the dialog which appears in selecting the Country preference will appear. First we create a layout for the dialog box. Following is the XML file for the same.
<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_margin=“@dimen/spacing_small”
  android:padding=“@dimen/spacing_small”
  android:orientation=“vertical”>

  <android.support.v7.widget.AppCompatSpinner
      android:id=“@+id/country_spinner”
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:layout_marginTop=“@dimen/spacing_small” />

</LinearLayout>
  • Now we create the CountryPreference constructor where we specify the UI Of the dialog box. It would include the positive and negative button.
private int layoutResourceId = R.layout.dialog_payment_country;
private int savedIndex;

public CountryPreference(Context context, AttributeSet attrs) {
  super(context, attrs, R.attr.preferenceStyle);
  setDialogLayoutResource(R.layout.dialog_payment_country);
  setPositiveButtonText(android.R.string.ok);
  setNegativeButtonText(android.R.string.cancel);
  setDialogIcon(null);
}
  • We override the method onSetInitialValue where we set the preference of the country in the shared preference. We call the method setCountry and pass the persisted value.
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
  setCountry(restorePersistedValue ? getPersistedInt(savedIndex) : (int) defaultValue);
  super.onSetInitialValue(restorePersistedValue, defaultValue);
}

 

public void setCountry(int index) {
  savedIndex = index;
  persistInt(index);
}
  • We create a class CountryPreferenceCompat which extends PreferenceDialogFragmentCompat. It is here that we initialize the spinner and set the adapter. It is here that we override the method onDialogClosed which should happen when the dialog box is closed. Following is the code for the same.
@Override
public void onDialogClosed(boolean positiveResult) {
  if (positiveResult) {
      DialogPreference preference = getPreference();
      if (preference instanceof CountryPreference) {
          CountryPreference countryPreference = ((CountryPreference) preference);
          countryPreference.setCountry(index);
      }
  }
}
  • In the PaymentPrefsFragment the code for initialization of the dialog is added. We override the onDisplayPreferenceDialog.
@Override
public void onDisplayPreferenceDialog(Preference preference) {
  CountryPreferenceFragmentCompat dialogFragment = null;
  if (preference instanceof CountryPreference)
      dialogFragment = CountryPreferenceFragmentCompat.newInstance(Constants.PREF_PAYMENT_COUNTRY);

  if (dialogFragment != null) {
      dialogFragment.setTargetFragment(this, 1);
      dialogFragment.show(this.getFragmentManager(),
          “android.support.v7.preference” +
              “.PreferenceFragment.DIALOG”);
  } else {
      super.onDisplayPreferenceDialog(preference);
  }
}
  • Now the PaymentCountry spinner can be seen on testing.

References

  1. Building Custom Preference https://www.hidroh.com/2015/11/30/building-custom-preferences-v7/
  2. StackOverflow solution https://stackoverflow.com/questions/16577173/spinnerpreference-how-to-embed-a-spinner-in-a-preferences-screen  

 

Continue Reading

Implementing the Order Receipt End Point in Orga App

In the  Open Event Orga App, I have implemented the Order Receipt endpoint with the help of which the organizer will be able to send the receipt of the ‘completed’ orders to the attendee via email. Initially the API was made in the server and then it was implemented in the the app.

Following steps were followed:

  • Firstly a method named sendReceipt was made in the OrderDetailFragment as follows. We pass in the orderIdentifier string as a parameter.
private void sendReceipt() {
  orderDetailViewModel.sendReceipt(orderIdentifier);
}
  • Now we implement 2 classes for OrderReceiptRequest and OrderReceiptResponse. The implementation is as follows. In the OrderReceiptRequest class we add just the orderIdentifier instance variable as the request involves just the order identifier parameter.
@Data
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class OrderReceiptRequest {

   public String orderIdentifier;
}
  • Now we implement the OrderReceiptResponse class which will consist of 2 parameters message and error.
public class OrderReceiptResponse {

  public String message;
  public String error;
}
  • In the OrderDetailsViewModel we add the following method. We create an object OrderReceipt where we pass the orderIdentifier. In the following statements we call the sendReceipts method of OrderRepositorry which takes in this OrderReceiptRequest as parameter.
public void sendReceipt(String orderIdentifier) {
  OrderReceiptRequest orderReceipt = new OrderReceiptRequest();
  orderReceipt.setOrderIdentifier(orderIdentifier);
  compositeDisposable.add(orderRepository.sendReceipt(orderReceipt)
      .doOnSubscribe(disposable -> progress.setValue(true))
      .doFinally(() -> progress.setValue(false))
      .subscribe(() -> success.setValue(“Email Sent!”),
          throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));
}
  • We then add the method sendReceipt in Order Repository which returns a Completable.
  • Now we implement the sendReceipt methid in OrderRepositoryImpl as follows. First we check whether the repository is connected or not. If not then a network error message is sent.Then the sendReceiptEmail method present in the Orderapi class is called where we pass the orderReceiptRequest object. The next step will show the adding of the API for this particular end point.
@Override
public Completable sendReceipt(OrderReceiptRequest orderReceiptRequest) {
  if (!repository.isConnected())
      return Completable.error(new Throwable(Constants.NO_NETWORK));

  return orderApi
          .sendReceiptEmail(orderReceiptRequest)
          .flatMapCompletable(
              var -> Completable.complete())
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread());
}
  • Now in the OrdersAPI interface the following API call is written. We pass the OrderReceiptRequest in the body and the respinse is collected in the OrderReceiptRequest class and diplayed as the outcome.
@POST(“attendees/send-receipt”)
Observable<OrderReceiptResponse> sendReceiptEmail(@Body OrderReceiptRequest orderReceiptRequest);
  • Certain UI changes also had to be done which are shown below.
<LinearLayout
  android:id=“@+id/emailReceiptLl”
  android:layout_width=“0dp”
  android:layout_height=“wrap_content”
  android:layout_weight=“1”
  android:layout_gravity=“center”
  android:gravity=“center”
  android:orientation=“vertical”>

  <Button
      android:id=“@+id/emailReceipt”
      android:layout_width=“30dp”
      android:layout_height=“30dp”
      android:background=“@drawable/ic_email”
      app:backgroundTint=“@color/materialcolorpicker__white” />

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:text=“@string/receipt”
      android:textAllCaps=“true”
      android:textSize=“10sp”
      android:textColor=“@color/materialcolorpicker__white”/>
</LinearLayout> 

Resources

  1. Using Observables and Completables https://github.com/ReactiveX/RxJava/wiki/What’s-different-in-2.0
  2. Medium article on RxJava https://blog.aritraroy.in/the-missing-rxjava-2-guide-to-supercharge-your-android-development-part-1-624ef326bff4
Continue Reading

Add Support for Online Events in Orga App

The Open Event Orga App  didn’t have support for online events. After the support for online events was added in the server, it got implemented in the orga app as well. The main difference between offline and online events is that offline events need location details while online events don’t need any location details.

Following steps were followed in its implementation:

  • Firstly the parameter isEventOnline was added to the Event.java model class. As the JSON tag name for online event was is_event_online the parameter was named as isEventOnline and was made of the type boolean.
public boolean isEventOnline;
  • Now a checkbox had to be added to the UI and so the following XML code was added to the event_details_step_one.xml. With the help of DataBinding it was checked whether the checkBox is checked or not. If it was checked then the Layout consisting of the Location Details was hidden and if not then it is an offline event and it can be shown.
<CheckBox
  android:id=“@+id/online_event”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:layout_marginTop=“@dimen/spacing_normal”
  android:onCheckedChanged=“@{ (switch, checked) -> event.setEventOnline(checked) }”
  android:padding=“@dimen/spacing_extra_small”
  android:text=“@string/event_online” />

<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:orientation=“vertical”
  android:visibility=“@{ onlineEvent.checked ? View.GONE : View.VISIBLE }”>

Now the same changes had to be done to UpdateEventsScreen as well. Hence the above XML code was added to it as well.

  • Now one thing that needed to be taken care of is that online events do not require location details and without which events cant get published. So in the EventDashboardPresenter it was made sure that if the event is online then it can get published even though no location details have been provided. The extra condition was added to the else if block of confirmToggle( ).
public void confirmToggle() {
  if (Event.STATE_PUBLISHED.equals(event.state)) {
      getView().switchEventState();
      getView().showEventUnpublishDialog();
  } else if (Utils.isEmpty(event.getLocationName()) && !event.isEventOnline) {
      getView().switchEventState();
      getView().showEventLocationDialog();
  } else {
      toggleState();
  }
}

Resources

  1. Medium article on using 2 way data binding https://medium.com/google-developers/android-data-binding-lets-flip-this-thing-dc17792d6c24
  2. Medium article on using Lombok in andorid https://medium.com/@wkrzywiec/project-lombok-how-to-make-your-model-class-simple-ad71319c35d5
Continue Reading

Removing Google Places from FDroid Flavor in Orga App

In the Open Event Orga App one of the libraries used was the Google Places by the Play Services. According to the FDroid inclusion policy, proprietary software such as Google Play Services cannot be included in the project and hence it needs to be removed or another alternatives needs to be found. Following steps were taken to remove the Places API and make sure that it is used only in the playStore version of the app.

Steps

  • Initially we will change the implementation in build.gradle file to playStoreImplementation to make sure that Places API is used in the playStore version.
playStoreImplementation “com.google.android.gms:play-services-places:${versions.play_services}”
  • A class LocationPicker.java is created in the fdroid and playStore directory. In the fdroid directory of the project we need to make sure that there is no implementation for the method launchPicker. Following is the code for this class. There is a method name getPlaces which has the following parameters : Activity and the Intent. It will return the Location object where we pass the the demo values for latitude and longitude and null value for the address string.
public class LocationPicker {

  private final double DEMO_VALUE = 1;

  public boolean launchPicker(Activity activity) {
      //do nothing
      return false;
  }

  @SuppressLint(“RestrictedApi”)
  public Location getPlace(Activity activity, Intent data) {
      return new Location(DEMO_VALUE, DEMO_VALUE, null);
  }

  public boolean shouldShowLocationLayout() {
      return true;
  }
}
  • Now we make the following class Location which will receive parameters from fdroid as well as playStore version. We will include this class in the Create package of events so that it can be shared by both LoactionPicker class of fdroid as well as playstore. The location class will take in 3 parameters i.e latitude, longitude and address. Its a normal POJO class.
public class Location {

  private double latitude;
  private double longitude;
  private CharSequence address;

  public Location(double latitude, double longitude, CharSequence address) {
      this.latitude = latitude;
      this.longitude = longitude;
      this.address = address;
  }

  public double getLatitude() {
      return latitude;
  }

  public double getLongitude() {
      return longitude;
  }

  public CharSequence getAddress() {
      return address;
  }

}
  • Now we need to implement the LocationPicker.java class in playStore directory so we need to implement the Google Places API in this particular class. Following is the implementation of the launchPicker class. We will create an instance of the googleApiAvailabilty and pass the activity context through it. If the places API is present and the connection is successful, new intent is made from where the place is selected.

We include the intent statement in the try block and catch the exceptions in the the catch block.

 

public boolean launchPicker(Activity activity) {
  int errorCode = googleApiAvailabilityInstance.isGooglePlayServicesAvailable(activity);

  if (errorCode == ConnectionResult.SUCCESS) {
      //SUCCESS
      PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
      try {
          activity.startActivityForResult(builder.build(activity), PLACE_PICKER_REQUEST);
          return true;
      } catch (GooglePlayServicesRepairableException e) {
          Timber.d(e, “GooglePlayServicesRepairable”);
      } catch (GooglePlayServicesNotAvailableException e) {
          Timber.d(“GooglePlayServices NotAvailable => Updating or Unauthentic”);
      }
  }
  return false;
};
  • Finally we modify the code in the EventCreateDetails class as well.

Resources:

  1. Official fdroid website https://f-droid.org/en/
  2. Places API Official documentation https://developers.google.com/places/web-service/autocomplete
Continue Reading

Implementing ProGuard in Open Event Orga App

In the Open Event Orga App there has been an issue with the size of the app. So to decrease the size of the app, we had to enable Proguard. By implementing proguard the size of the app has reduced from 5.9 Mb to 2.9 Mb. The following article shows the steps taken to implement Proguard.

  • Firstly the following parameters need to set to true in the the app level build.gradle file.
  release {
      minifyEnabled true
      shrinkResources true
      proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’

The built-in shrinker in minify enabled removes the dead code. It doesn’t obfuscate or optimizes the code. In this section of code, we also set the proguard-rules.pro file in which all the rules regarding code obfuscation are set. The file is a custom file unlike the default proguard-android.txt file which is already a part of Android Studio.

  • Now we head over to the proguard-rules.pro file. But first we need to understand the 2 most important terms –dontwarn and –keep class.

 

2.1 -dontwarn → When proguard is implemented, code obfuscation takes place and the name of the classes get shortened to single letters. Because of this there might be a few conflicts. But there might be some unresolved references in the obfuscated code. dontwarn ignores all these references.

2.2 –keep class → Suppose there are certain classes which shouldn’t be obfuscated then they are preceded with keep class annotation.

 

  • In the proguard-rules.pro file the following lines are added
# Platform calls Class.forName on types which do not exist on Android to determine platform.
dontnote retrofit2.Platform
# Platform used when running on Java 8 VMs. Will not be used at runtime.
dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
keepattributes Exceptions

The keepattributes annotation is basically used for Generics which are JAVA JDK 5 and higher. It basically prevents the code from obfuscation.

  • Now Retrofit classes also need to be obfuscated. For that we add the following lines of code. The obfuscation considerably reduces the size of the app.
-dontwarn okio.**

-dontwarn com.squareup.okhttp.**
  • The model classes need to be prevented from getting obfuscated or else it may lead to app crash. Hence these classes need to be kept and so we add the following lines of code to prevent app crash. The link for the PR which fixed the app crash cause due to buggy proguard rule.
keep class org.fossasia.openevent.app.data.** {
*;
}
  • The orga app uses the Jackson library for parsing JSON data. Proguard rules also needs to be applied for this library.
# Jackson
keepattributes *Annotation*,EnclosingMethod,Signature
keepnames class com.fasterxml.jackson.** { *; }
dontwarn com.fasterxml.jackson.databind.**
keep class org.codehaus.** { *; }
-keepclassmembers public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility {
  public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *;
}
keep class com.github.jasminb.** { *; }
  • The other libraries used in the app also have specific proguard rules. These are listed below.
# General
keepattributes SourceFile,LineNumberTable,*Annotation*,EnclosingMethod,Signature,Exceptions,InnerClasses
keep class android.support.v7.widget.SearchView { *; }

keep class com.github.mikephil.charting.** { *; }
G
keep public class * extends com.bumptech.glide.module.AppGlideModule
keep class com.bumptech.glide.GeneratedAppGlideModuleImpl

 

References:

  1. Medium article by Jon Finerty https://medium.com/@jonfinerty/beginner-to-proguard-b3327ff3a831

Official Developer documentation  https://developer.android.com/studio/build/shrink-code

Continue Reading

Using Android Architecture Components in Organizer App

In the Open Event Organizer Android App there is an issue with the Memory leaks, Data persistence on configuration changes and difficulty in managing the Activity lifecycle. So as to deal with these issues we have implemented Android Architecture Components.

The first step towards moving on with AAC’s was the conversion of a presenter class to a ViewModel class and then implementing LiveData in the refactored class.

LiveData

It is an Observable data holder. It notifies the observers whenever there is change in the data so that the UI can be updated.

Livedata is also bound to the lifecycle which means that it will be observing changes only when the activity is in started or resumed state and hence there is no chance of memory leaks or null pointer exceptions.

ViewModel

The ViewModel class is designed to hold and manage UI-related data in a life-cycle conscious way. This allows data to survive configuration changes such as screen rotations.

In the following I’ll be explaining how the LoginViewModel class was made in the Orga App.

Steps

  • Creating one’s own custom ViewModelFactory. This is done so as to follow the Single Responsibility Principle. This custom class extends the ViewModelProvider.Factory and handles the creation of View Models. Adding this class also ensures that a Constructor can also get injected in the View Model class.
@Singleton
public class OrgaViewModelFactory implements ViewModelProvider.Factory {

  private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

  @Inject
  public OrgaViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
      this.creators = creators;
  }

  @NonNull
  @Override
  @SuppressWarnings({“unchecked”, “PMD.AvoidThrowingRawExceptionTypes”, “PMD.AvoidCatchingGenericException”})
  public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
      Provider<? extends ViewModel> creator = creators.get(modelClass);
      if (creator == null) {
          for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
              if (modelClass.isAssignableFrom(entry.getKey())) {
                  creator = entry.getValue();
                  break;
              }
          }
      }
      if (creator == null) {
          throw new IllegalArgumentException(“unknown model class “ + modelClass);
      }
      try {
          return (T) creator.get();
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
  }
}
  • Injecting this custom ViewModelFactory into the ViewModelModule. Adding the method bindLoginViewModel with the LoginViewModel as its parameter. Always add any new ViewModel class into the ViewModelModule otherwise it might show DaggerAppComponent errors.
@Module
public abstract class ViewModelModule {

  @Binds
  @IntoMap
  @ViewModelKey(LoginViewModel.class)
  public abstract ViewModel bindLoginViewModel(LoginViewModel loginViewModel);

  @Binds
  public abstract ViewModelProvider.Factory bindViewModelFactory(OrgaViewModelFactory factory);

}
  1. Refactoring the LoginPresenter to LoginViewModel class and extending it to ViewModel.
  2. In the fragment class injecting the ViewModelProviderFactory.
@Inject
ViewModelProvider.Factory viewModelFactory;
  • Pass this parameter in the ViewModelProviders.of( ) method as follows:
loginFragmentViewModel = ViewModelProviders.of(this, viewModelFactory).get(LoginViewModel.class);
  • Now the only task in hand is the use of LiveData in the ViewModels and observing the LiveData from the Fragments. In the following LiveData has been applied to observe the state of Progress Bar. When the login button is pressed, the value of MutableLiveData<Boolean> progress is set to true.
public void login() {
  compositeDisposable.add(loginModel.login(login)
      .doOnSubscribe(disposable -> progress.setValue(true))
      .doFinally(() -> progress.setValue(false))
      .subscribe(() -> isLoggedIn.setValue(true),
          throwable -> error.setValue(ErrorUtils.getMessage(throwable))));
}

 

  • This change in state is observed by the following code in the fragment class:
loginFragmentViewModel.getProgress().observe(this, this::showProgress);

On observing this, the showProgress is called which handles the visibility if the progress bar. Currently as the progress value was set to True, the progress bar is visible till the processing goes on.

  • Once the login takes place the progress of the LoginViewModel is set to false and the progress bar gets hidden, again which gets observed in the fragment class.

References:

https://android.jlelse.eu/android-architecture-components-livedata-1ce4ab3c0466

Continue Reading

Adding Open Event Orga App as a Product Flavor on FDroid

To release the orga app on fdroid, product flavors need to be added to the app. This means that 2 different packages need to be maintained that handle the code for fdroid and playstore separately. For eg. There are certain proprietary software that are used in the app such as Google Play services which won’t get accepted on fdroid. Hence alternatives need to be found out and separate code has to be maintained.  

Steps:

  • Go to the build.gradle file where in the Android section add the following line of code in the groovy language. We specify the flavorDimension to be default and then add the productFlavor tag. Inside this tag there will be 2 more sub tags namely fdroid and playStore. These are the 2 different flavors we will be maintaining in the app. Because of this command, 4 different build variants will be created namely : fdroidDebug, fdroidRelease, playStoreDebug and playStoreRelease. The dimension for each is specified to be default.
flavorDimensions “default”

productFlavors {
  fdroid {
      dimension “default”
  }

  playStore {
      dimension “default”
  }
}
  • Now as testCoverage is being used in the app the file name also need to be changed from debug to playStore. So firstly go to the travis.yml file and change the testDebugUnitCoverage command to testPlayStoreDebugUnitCoverage. Hence when the travis will build, the modified command will be executed rather than the old command. We could also have used testfdroidDebugUnitCoverage.Following is the code snippet from the travis.yml file.
script:
./gradlew build
./gradlew testPlayStoreDebugUnitTestCoverage
  • Now command for testCoverage also needs to be changed in the config.yml. So navigate to this file and modify the command.
– run:
  name: Test Coverage
  command: ./gradlew testPlayStoreDebugUnitTestCoverage
  • Now navigate to the update-apk.sh script where we will change the name of the output apk in case of fdroid build and playStore build. As can be seen below we have first changed the name of the json file in each build. Also the name of the folder where they get generated are changed.
\cp -r ../app/build/outputs/apk/playStore/*/**.apk .
\cp -r ../app/build/outputs/apk/fdroid/*/**.apk .
\cp -r ../app/build/outputs/apk/playStore/debug/output.json playStore-debug-output.json
\cp -r ../app/build/outputs/apk/playStore/release/output.json playStore-release-output.json
\cp -r ../app/build/outputs/apk/fdroid/debug/output.json fdroid-debug-output.json
\cp -r ../app/build/outputs/apk/fdroid/release/output.json fdroid-release-output.json
  • Also the apk names in fastlane  are changed for playStore with the help of the following code.
gem install fastlane
fastlane supply –apk app-playStore-release.apk –track alpha –json_key ../scripts/fastlane.json –package_name $PACKAGE_NAME

Resources

  1. Official fdroid website https://f-droid.org/en/
  2. Google Developer website https://developer.android.com/studio/build/build-variants
Continue Reading

Implement Tests for Feedback List in Open Event Orga App

In the Open Event Orga App test have been written for all the presenters and viewmodel classes to ensure that the implemented functionalities work well.

In the following blog post I have discussed one particular test which I implemented which is the FeedbackList Presenter Test.

Implementation

  1. Instantiation of the variables.
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock
public FeedbackListView feedbackListView;
@Mock
public FeedbackRepository feedbackRepository;

We should first know the meaning of the Annotations being used:

@Rule : It tells mockito to create the mocks based on the @Mock annotation. This annotation always needs to be used.

@Mock: It tells Mockito to mock the FeedbackListView interface and FeedbackRepository class.

Here we are mocking 3 classes namely: MockitoRule, FeedbackListView, FeedbackRepository.

Before moving forward we first need to understand the meaning of Mock. A mock object is a dummy implementation for an interface or a class in which you define the output of certain method calls. Mock objects are configured to perform a certain behavior during a test. They typically record the interaction with the system and tests can validate that.

 

private static final List<Feedback> FEEDBACKS = Arrays.asList(
  Feedback.builder().id(2L).comment(“Amazing!”).build(),
  Feedback.builder().id(3L).comment(“Awesome!”).build(),
  Feedback.builder().id(4L).comment(“Poor!”).build()
);

The list of feedbacks is populated with demo values which can be used for testing purpose later.

2) The @Before annotation is applied before the set up. Before any tests are created, the setUp( ) is executed. A feedbackListPresenter object is created and the required parameters are passed. The RxJava Plugin’s setIoSchedulerHandler, setComputationSchedulerHandler and setInitmainThreadSchedulerHandler use the Scheduler.Trampoline( ) .  It lets the internal call Observable call to end before asserting the result.

setIOSchedulerHandler( ) -> It basically is a type of Scheduler which handles the Input and Output of the RxJava code.

setComputationSchedulerHandler( ) -> It is another Scheduler which handles the computations which are carried out during call to RxJava methods.

setInitMainThreadSchedulerHandler( ) -> It is called to notify the Scheduler that the IO operations would be carried out on the main thread.

@Before
public void setUp() {
  feedbackListPresenter = new FeedbackListPresenter(feedbackRepository);
  feedbackListPresenter.attach(ID, feedbackListView);

  RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
  RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
  RxAndroidPlugins.setInitMainThreadSchedulerHandler(schedulerCallable -> Schedulers.trampoline());
}

Some of the tests are discussed below:

→  The following test is written to ensure that the feedback list gets updated automatically after a feedback is received.

@Test
public void shouldLoadFeedbackListAutomatically() {
  when(feedbackRepository.getFeedbacks(anyLong(), anyBoolean())).thenReturn(Observable.fromIterable(FEEDBACKS));

  feedbackListPresenter.start();

  verify(feedbackRepository).getFeedbacks(ID, false);
}

As can be seen above , I have used the when and return functionality of Mockito. It is basically used to check the return type of the object. So when the required parameters are passed in the getFeedback( ) , then the return type of what is expected is mentioned in the thenReturn( ).

verify ensures that the getFeedback( ) is called on the feedbackfeedbackRepository mock only.

→ The following test is written to ensure that there is an error message on loading data after swipe refresh is made. Firstly the list of feedbacks is fetched from the feedbackRepository with the help of getFeedbacks( ) where the parameters event id and the boolean variable true are passed. Then the thenReturn( ) has the statement Observable.error(Logger.TEST_ERROR) which is actually written to specify the expected result we want i.e in this case we are expecting the TEST_ERROR statement as a response and hence it is written before.

At the end it is verified using the statement verify statement where the feedbackListView is passed and the error is captured.

 

@Test
public void shouldShowErrorMessageOnSwipeRefreshError() {
  when(feedbackRepository.getFeedbacks(ID, true)).thenReturn(Observable.error(Logger.TEST_ERROR));

  feedbackListPresenter.loadFeedbacks(true);

  verify(feedbackListView).showError(Logger.TEST_ERROR.getMessage());
}

3) After the tests have been applied, the RxJava plugins are reset.

@After
public void tearDown() {
  RxJavaPlugins.reset();
  RxAndroidPlugins.reset();

}

Resources:

→ Mockito tutorial :

http://www.vogella.com/tutorials/Mockito/article.html

→ Testing in RxJava:

https://stackoverflow.com/questions/40233956/how-to-use-schedulers-trampoline-inrxjava

Continue Reading

Implementing Check-in time chart in Orga App

Earlier in the Open event orga app there were no charts present to track the check-in time of the attendees. Hence it was quite cumbersome for the organiser to track the people and at what time they have checked-in. Using this feature of check-in time chart, the process has become quite easier.

Whenever an attendee checks-in, the data point is added to the chart and a chart is plotted. The Y-axis shows the number of attendees and the X-axis shows the time at which they have checked-in.

To implement this feature I have taken use of the MPAndroidCharts library which makes the job a lot easier. Following steps were followed to implement the charts:

  • Adding the following Library dependency in the build.gradle file
implementation “com.github.PhilJay:MPAndroidChart:v3.0.3”
  • Now the following code is added to the ticket_analytics.xml file. This is done so that the UI of the charts can be created. The following XML file consists of the LineChart XML tag which shows the check-in time chart on screen. Also the labelling of the axis needs to be done, so the X-axis is explicitly named as “TIME”.
<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:orientation=“vertical”>

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:layout_marginLeft=“@dimen/spacing_normal”
      android:layout_marginStart=“@dimen/spacing_normal”
      android:layout_marginTop=“@dimen/spacing_normal”
      android:text=“@string/check_in_summary”
      android:textAllCaps=“true”
      android:textSize=“@dimen/text_size_small” />

  <com.github.mikephil.charting.charts.LineChart
      android:id=“@+id/chartCheckIn”
      android:layout_width=“match_parent”
      android:layout_height=“200dp”
      android:layout_marginEnd=“@dimen/spacing_normal”
      android:layout_marginLeft=“@dimen/spacing_normal”
      android:layout_marginRight=“@dimen/spacing_normal”
      android:layout_marginStart=“@dimen/spacing_normal” />

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:layout_gravity=“center”
      android:layout_marginBottom=“8dp”
      android:layout_marginTop=“8dp”
      android:text=“@string/check_in_time”
      android:textSize=“10sp” />

  <LinearLayout
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:orientation=“vertical”>

      <FrameLayout
          android:layout_width=“match_parent”
          android:layout_height=“1dp”
          android:background=“@color/color_shadow” />

      <FrameLayout
          android:layout_width=“match_parent”
          android:layout_height=“@dimen/spacing_small”
          android:background=“@color/color_bottom_surface” />
  </LinearLayout>
</LinearLayout>
  • Now the a method loadCheckIn( )  chart needs to added to the EventsDashboardPresenter. This is called from the EventsDashboardFragment. The loadDataCheckIn( ) is created in the ChartAnalyzer class. We pass getId( ) as the parameter.
private void loadCheckInTimesChart() {
  chartAnalyser.showChart(getView().getCheckinTimeChartView());
  chartAnalyser.loadDataCheckIn(getId())
      .compose(disposeCompletable(getDisposable()))
      .subscribe(() -> {
          getView().showChartCheckIn(true);
          chartAnalyser.showChart(getView().getCheckinTimeChartView());
      }, throwable -> getView().showChartCheckIn(false));
}
  • Now we add the method loadDataCheckIn( ) in the ChartAnalyzer class. This method returns a Completable and takes eventId as the single parameter.
public Completable loadDataCheckIn(long eventId) {
  clearData();
  isCheckinChart = true;
  return getAttendeeSource(eventId).doOnNext(attendee -> {
     String checkInTime = attendee.getCheckinTimes();
     int length = checkInTime.split(“,”).length;
     String latestCheckInTime = checkInTime.split(“,”)[length – 1];
     error = checkInTime == null ? true : false;
     addDataPointForCheckIn(checkInTimeMap, latestCheckInTime);
  })
    .toList()
    .doAfterSuccess(attendees -> this.attendees = attendees)
    .toCompletable()
    .doOnComplete(() -> {
        if (error)
            throw new IllegalAccessException(“No checkin’s found”);
        checkInDataSet = setDataForCheckIn(checkInTimeMap, “check-in time”);
        prepare();
    });
}

It calls the getAttendeeSource( ) which further gives a call to the method getAttendees( ) from the AttendeeRepository. All the inormation related to the attendees is returned from which the check-in times is extracted. The check-in times are returned in comma separated form and hence we need to extract the first element of the sequence.

private Observable<Attendee> getAttendeeSource(long eventId) {
  if (attendees == null || attendees.isEmpty())
      return attendeeRepository.getAttendees(eventId, false);
  else
      return Observable.fromIterable(attendees);
}
  • After the success of loading the attendees, the method addDataPointForCheckIn is called. We call it by inserting the parameters Map<Integer, Long> and the dateString which we had passed from the loadDataCheckIn( ). Following is the code for it. A map is created out of the data. The key in the map is the time and value is the number of people who have checked-in at that time.
private void addDataPointForCheckIn(Map<Integer, Long> map, String dateString) {
  int hour = DateUtils.getDate(dateString).getHour();
  Long numberOfCheckins = map.get(hour);

  if (numberOfCheckins == null)
      numberOfCheckins = 0L;

  map.put(hour, ++numberOfCheckins);
}
  • After the map is created it is passed on to the setDataForCheckIn( ) and the label is provided as “check-in times”. Following is the code for setDataForCheckIn( ). All the values of the map are parsed and a new entry object is made in which the value of the key and value pairs are passed. This object is then added to the ArrayList.
private LineDataSet setDataForCheckIn(Map<Integer, Long> map, String label) throws ParseException {
  List<Entry> entries = new ArrayList<>();
  for (Map.Entry<Integer, Long> entry : map.entrySet()) {
      entries.add(new Entry(entry.getKey(), entry.getValue()));
  }
  Collections.sort(entries, new EntryXComparator());

  // Add a starting point 2 hrs ago
  entries.add(0, new Entry(entries.get(0).getX() – 2, 0));
  return new LineDataSet(entries, label);
}
  • The object LineDataSet is returned with all the entries stored in the ArrayList. Now the prepare( ) is called. It is in this method that we add the code for the UI of the chart.
private void prepare() {
  if (isCheckinChart) {
      initializeLineSet(checkInDataSet, R.color.light_blue_500, R.color.light_blue_100);
      lineData.addDataSet(checkInDataSet);
  } else {
      initializeLineSet(freeSet, R.color.light_blue_500, R.color.light_blue_100);
      initializeLineSet(paidSet, R.color.purple_500, R.color.purple_100);
      initializeLineSet(donationSet, R.color.red_500, R.color.red_100);
      lineData.addDataSet(freeSet);
      lineData.addDataSet(paidSet);
      lineData.addDataSet(donationSet);
      lineData.setDrawValues(false);
  }
lineData.setDrawValues(false);
}

initializeLineSet( ) is the method where we add the color which will be used for plotting the data set.In our case the color is blue.

  • We also need to plot the time stamps in the X-axis. Unfortunately MPAndroidCharts doesn’t have a functionality for that. So to handle it an inner class MyMyAxisValueFormatter is created which extends IAxisValueFormatter. Following is the code for it.
public class MyAxisValueFormatter implements IAxisValueFormatter {

  @Override
  public String getFormattedValue(float value, AxisBase axis) {
      if (value < 0)
          return values[24 + (int) value];
      return values[(int) value];
  }
}

The values array list consists of the time stamps that will be present on the X-Axis.

String[] values = new String[] {“00:00”, “1:00”, “2:00”, “3:00”, “4:00”, “5:00”, “6:00”, “7:00”, “8:00”, “9:00”, “10:00”, “11:00”, “12:00”, “13:00”, “14:00”, “15:00”, “16:00”, “17:00”, “18:00”, “19:00”, “20:00”, “21:00”, “22:00”, “23:00”};
  • Finally the showChart( ) is called in which we specify details regarding the grid color, legend, visibility of X and Y axis etc. We also specify the animation that needs to be done whenever the chart is on screen.

 

public void showChart(LineChart lineChart) {
  lineChart.setData(lineData);
  lineChart.getXAxis().setEnabled(true);
  lineChart.getAxisRight().setEnabled(false);
  lineChart.getDescription().setEnabled(false);
  lineChart.getLegend().setEnabled(false);

  YAxis yAxis = lineChart.getAxisLeft();
  yAxis.setGridLineWidth(1);
  yAxis.setGridColor(Color.parseColor(“#992ecc71”));
  if (!isCheckinChart)
      if (maxTicketSale > TICKET_SALE_THRESHOLD)
          yAxis.setGranularity(maxTicketSale / TICKET_SALE_THRESHOLD);
  else {
      XAxis xAxis = lineChart.getXAxis();
      xAxis.setValueFormatter(new MyAxisValueFormatter());
      yAxis.setGranularity(1);
      lineChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
      lineChart.getXAxis().setGranularity(1f);
  }

  Description description = new Description();
  description.setText(“”);
  lineChart.setDescription(description);
  lineChart.animateY(1000);
}

 

Continue Reading

Implementing Horizontal Stepper In Open Event Orga App

Currently while event creation the user has to fill a form, all of which is present on a single screen. This process is a bit cumbersome. To improve the user interface and make the app more interactive, a horizontal stepper is implemented in the app. Implementing this would also make it consistent with the frontend project. Horizontal stepper actually divides the current fragment into 3 different fragments and the contents are distributed over all of them. The user can navigate through these fragments either by swiping or by using NEXT and PREV button which are present on the screen.

To implement this the following steps are taken:

  • I am using the following library in the app. This is added to the build.gradle file and sync is done.
//stepper
implementation ‘com.github.badoualy:stepper-indicator:1.0.7’
  • Now changes need to be made to the CreateEventActivity which acts as the base activity for the 3 fragments which we will be implementing later. We hover over to the activity_create.xml where we will add the Stepper tag and the viewpager. Following code is written there.
<android.support.v4.view.ViewPager
  android:id=“@+id/pager”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_alignParentLeft=“true”
  android:layout_alignParentStart=“true”
  android:layout_alignParentTop=“true”
  android:layout_marginTop=“100dp” />

<com.badoualy.stepperindicator.StepperIndicator
  android:id=“@+id/stepper_indicator”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_marginLeft=“16dp”
  android:layout_marginRight=“16dp”
  android:layout_marginTop=“32dp”
  app:stpi_animDuration=“200”
  app:stpi_circleColor=“@color/blue_200”
  app:stpi_circleRadius=“10dp”
  app:stpi_indicatorColor=“@color/green_500”
  app:stpi_labels=“@array/stepLabels”
  app:stpi_showDoneIcon=“true” />
  • To add the NEXT, PREVIOUS and SUBMIT buttons on the screen for navigation we will add the following code.
<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:layout_gravity=“bottom”
  android:orientation=“horizontal”
  android:background=“@color/color_accent”>

  <Button
      android:id=“@+id/btn_prev”
      android:layout_width=“0dp”
      android:layout_weight=“0.5”
      android:layout_height=“wrap_content”
      android:background=“@color/color_accent”
      android:text=“Previous”
      android:layout_gravity=“bottom|start”
      android:textColor=“@android:color/white” />

  <Button
      android:id=“@+id/btn_next”
      android:layout_width=“0dp”
      android:layout_height=“wrap_content”
      android:background=“@color/color_accent”
      android:text=“Next”
      android:layout_weight=“0.5”
      android:layout_gravity=“bottom”
      android:textColor=“@android:color/white” />

  <Button
      android:id=“@+id/btn_submit”
      android:layout_width=“0dp”
      android:layout_height=“wrap_content”
      android:background=“@color/color_accent”
      android:text=“Create”
      android:layout_weight=“0.5”
      android:visibility=“gone”
      android:layout_gravity=“bottom”
      android:textColor=“@android:color/white” />

</LinearLayout>
  • A pager adapter class is made which extends the FragmentPagerAdapter. This class handles the position of the adapter and the fragments which need to be displayed at each step. All this is handled in the getItem( ) method.
public class PagerAdapter extends FragmentPagerAdapter {
  public PagerAdapter(FragmentManager fm) {
      super(fm);
  }

  @Override
  public Fragment getItem(int position) {
      switch (position) {
          case 0:
              return EventDetailsStepOne.newInstance();
          case 1:
              return EventDetailsStepTwo.newInstance();
          case 2:
              return EventDetailsStepThree.newInstance();
          default:
              return null;
      }
  }

  @Override
  public int getCount() {
      return 3;
  }
}
  • To link stepper indicator with the view pager the following code is added
assert pager != null;
pager.setAdapter(new PagerAdapter(getSupportFragmentManager()));

indicator.setViewPager(pager, pager.getAdapter().getCount());
  • A page listener is added to the viewpager so that the visibility of the buttons can be handled easily.
pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
  @Override
  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
      if (position == 0) {
          btnPrev.setVisibility(View.GONE);
          btnNext.setVisibility(View.VISIBLE);
          btnSubmit.setVisibility(View.GONE);
      } else if (position == 1) {
          btnPrev.setVisibility(View.VISIBLE);
          btnNext.setVisibility(View.VISIBLE);
          btnSubmit.setVisibility(View.GONE);
      } else if (position == 2) {
          btnPrev.setVisibility(View.VISIBLE);
          btnNext.setVisibility(View.GONE);
          btnSubmit.setVisibility(View.VISIBLE);
      }
  }
  • Now 3 different fragments are created along with their layouts and a SharedViewModel is also added which is shared by the fragments. SharedViewModel is different from a normal ViewModel where we need to provide the Activity context in the ViewModelProviders.of(context).

 

  • The current CreateEventFragment is divided into 3 fragments namely EventDetailsStepOne, EventDetailsStepTwo and EventDetailsStepThree and the code for all of them is redistributed to all of these fragments. So each and every fragment now has a different responsibility which is handled separately.

 

  • Now the current CreateEventFragment is changed to UpdateEventFragment. This will now only handle the editing of events when the Edit Event is selected.

Resources:

  1. Library for implementing horizontal stepper

https://github.com/badoualy/stepper-indicator

Continue Reading
  • 1
  • 2
Close Menu