Restoring State after Orientation Change in Loklak Wok Android

During orientation change i.e. from portrait to landscape mode in Android, the current activity restarts again. As the activity restarts again, all the defined variables loose their previous value, for example the scroll position of a RecyclerView, or the data in the rows of RecyclerView etc. Just imagine a user searched some tweets in Loklak Wok Android, and as the user’s phone is in “Auto rotation” mode, the orientation changes from portrait to landscape. As a result of this, the user loses the search result and has to do the search again. This leads to a bad UX.

Saving state in onSavedInstanceState

The state of the app can be saved by inserting values in a Bundle object in onSavedInstanceState callback. Inserting values is same as adding elements to a Map in Java. Methods like putDouble, putFloat, putChar etc. are used where the first parameter is a key and the second parameter is the value we want to insert.

@Override
public void onSaveInstanceState(Bundle outState) {
   if (mLatitude != null && mLongitude != null) {
       outState.putDouble(PARCELABLE_LATITUDE, mLatitude);
       outState.putDouble(PARCELABLE_LONGITUDE, mLongitude);
   }
...
}

 

The values can be retrieved back when onCreate or onCreateView of the Activity or Fragment is called. Bundle object in the callback parameter is checked, whether it is null or not, if not the values are retrieved back using the keys provided at the time of inserting. The latitude and longitude of a location in TweetPostingFragment are retrieved in the same fashion

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
   ...
   if (savedInstanceState != null) { // checking if bundle is null
       // extracting from bundle
       mLatitude = savedInstanceState.getDouble(PARCELABLE_LATITUDE);
       mLongitude = savedInstanceState.getDouble(PARCELABLE_LONGITUDE);
       // use extracted value
   }
}

Restoring Custom Objects, using Parcelable

But what if we want to restore custom object(s). A simple option can be serializing the objects using the native Java Serialization or libraries like Gson. The problem in these cases is performance, they are quite slow. Parcelable can be used, which leads the pack in performance and moreover it is provided by Android SDK, on top of that, it is simple to use.

The objects of class which needs to be restored implements Parcelable interface and the class must provide a static final object called CREATOR which implements Parcelable.Creator interface.

writeToParcel and describeContents method need to be override to implement Parcelable interface. In writeToParcel method the member variables are put inside the parcel, in our case describeContents method is not used, so, simply 0 is returned. Status class which stores the data of a searched tweet implements parcelable.

@Override
public int describeContents() {
   return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
   dest.writeString(mText);
   dest.writeInt(mRetweetCount);
   dest.writeInt(mFavouritesCount);
   dest.writeStringList(mImages);
   dest.writeParcelable(mUser, flags);
}

 

NOTE: The order in which variables are pushed into Parcel needs to be maintained while variables are extracted from the parcel to recreate the object. This is the reason why no “key” is required to push data into a parcel as we do in bundle.

The CREATOR object implements the creation of object from a Parcel. The CREATOR object overrides two methods createFromParcel and newArray. createFromParcel is the method in which we implement the way an object is created from a parcel.

public static final Parcelable.Creator<Status> CREATOR = new Creator<Status>() {
   @Override
   public Status createFromParcel(Parcel source) {
       return new Status(source); // a private constructor to create object from parcel
   }

   @Override
   public Status[] newArray(int size) {
       return new Status[size];
   }
};

 

The private constructor, note that the order in which variables were pushed is maintained while retrieving the values.

private Status(Parcel source) {
   mText = source.readString();
   mRetweetCount = source.readInt();
   mFavouritesCount = source.readInt();
   mImages = source.createStringArrayList();
   mUser = source.readParcelable(User.class.getClassLoader());
}

 

The status objects are restored the same way, latitude and longitude were restored. putParcelableArrayList in onSaveInstaceState and getParcelableArrayList in onCreateView methods are used to push into Bundle object and retrieve from Bundle object respectively.

@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   ArrayList<Status> searchedTweets = mSearchCategoryAdapter.getStatuses();
   outState.putParcelableArrayList(PARCELABLE_SEARCHED_TWEETS, searchedTweets);
   ...
}


// retrieval of the pushed values in bundle
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                            Bundle savedInstanceState) {
   ...
   if (savedInstanceState != null) {
       ...
       List<Status> searchedTweets =
               savedInstanceState.getParcelableArrayList(PARCELABLE_SEARCHED_TWEETS);
       mSearchCategoryAdapter.setStatuses(searchedTweets);
   }
   ...
   return view;
}

Resources:

Testing Presenter of MVP in Loklak Wok Android

Imagine working on a large source code, and as a new developer you are not sure whether the available source code works properly or not, you are surrounded by questions like, Are all these methods invoked properly or the number of times they need to be invoked? Being new to source code and checking manually already written code is a pain. For cases like these unit-tests are written. Unit-tests check whether the implemented code works as expected or not. This blog post explains about implementation of unit-tests of Presenter in a Model-View-Presenter (MVP) architecture in Loklak Wok Android.

Adding Dependencies to project

In app/build.gradle file

defaultConfig {
   ...
   testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

dependencies {
   ...
   androidTestCompile 'org.mockito:mockito-android:2.8.47'
   androidTestCompile 'com.android.support:support-annotations:25.3.1'
   androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}

Setup for Unit-Tests

The presenter needs a realm database and an implementation of LoklakAPI interface. Along with that a mock of the View is required, so as to check whether the methods of View are called or not.

The LoklakAPI interface can be mocked easily using Mockito, but the realm database can’t be mocked. For this reason an in-memory realm database is created, which will be destroyed once all unit-test are executed. As the presenter is required for each unit-test method we instantiate the in-memory database before all the tests start i.e. by annotating a public static method with @BeforeClass, e.g. setDb method.

@BeforeClass
public static void setDb() {
   Realm.init(InstrumentationRegistry.getContext());
   RealmConfiguration testConfig = new RealmConfiguration.Builder()
           .inMemory()
           .name("test-db")
           .build();
   mDb = Realm.getInstance(testConfig);
}

 

NOTE: The in-memory database should be closed once all unit-tests are executed. So, for closing the databasse we create a public static method annotated with @AfterClass, e.g. closeDb method.

@AfterClass
public static void closeDb() {
   mDb.close();
}

 

Now, before each unit-test is executed we need to do some setup work like instantiating the presenter, a mock instance of API interface generated  by using mock static method and pushing in some sample data into the database. Our presenter uses RxJava and RxAndroid which depend on IO scheduler and MainThread scheduler to perform tasks asynchronously and these schedulers are not present in testing environment. So, we override RxJava and RxAndroid to use trampoline scheduler in place of IO and MainThread so that our test don’t encounter NullPointerException. All this is done in a public method annotated with @Before e.g. setUp.

@Before
public void setUp() throws Exception {
   // mocking view and api
   mMockView = mock(SuggestContract.View.class);
   mApi = mock(LoklakAPI.class);

   mPresenter = new SuggestPresenter(mApi, mDb);
   mPresenter.attachView(mMockView);

   queries = getFakeQueries();
   // overriding rxjava and rxandroid
   RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
   RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());

   mDb.beginTransaction();
   mDb.copyToRealm(queries);
   mDb.commitTransaction();
}

 

Some fake suggestion queries are created which will be returned as observable when API interface is mocked. For this, simply two query objects are created and added to a List after their query parameter is set. This is implemented in getFakeQueries method.

private List<Query> getFakeQueries() {
   List<Query> queryList = new ArrayList<>();

   Query linux = new Query();
   linux.setQuery("linux");
   queryList.add(linux);

   Query india = new Query();
   india.setQuery("india");
   queryList.add(india);

   return queryList;
}

 

After that, a method is created which provides the created fake data wrapped inside an Observable as implemented in getFakeSuggestionsMethod method.

private Observable<SuggestData> getFakeSuggestions() {
   SuggestData suggestData = new SuggestData();
   suggestData.setQueries(queries);
   return Observable.just(suggestData);
}

 

Lastly, the mocking part is implemented using Mockito. This is really simple, when and thenReturn static methods of mockito are used for this. The method which would provide the fake data is invoked inside when and the fake data is passed as a parameter to thenReturn. For example, stubSuggestionsFromApi method

private void stubSuggestionsFromApi(Observable observable) {
   when(mApi.getSuggestions(anyString())).thenReturn(observable);
}

Finally, Unit-Tests

All the tests methods must be annotated with @Test.

Firstly, we test for a successful API request i.e. we get some suggestions from the Loklak Server. For this, getSuggestions method of LoklakAPI is mocked using stubSuggestionFromApi method and the observable to be returned is obtained using getFakeSuggestions method. Then, loadSuggestionFromAPI method is called, the one that we need to test. Once loadSuggestionFromAPI method is invoked, we then check whether the method of the View are invoked inside loadSuggestionFromAPI method, this is done using verify static method. The unit-test is implemented in testLoadSuggestionsFromApi method.

@Test
public void testLoadSuggestionsFromApi() {
   stubSuggestionsFromApi(getFakeSuggestions());

   mPresenter.loadSuggestionsFromAPI("", true);

   verify(mMockView).showProgressBar(true);
   verify(mMockView).onSuggestionFetchSuccessful(queries);
   verify(mMockView).showProgressBar(false);
}

 

Similarly, a failed network request for obtaining is suggestions is tested using testLoadSuggestionsFromApiFail method. Here, we pass an IOException throwable – wrapped inside an Observable – as parameter to stubSuggestionsFromApi.

@Test
public void testLoadSuggestionsFromApiFail() {
   Throwable throwable = new IOException();
   stubSuggestionsFromApi(Observable.error(throwable));

   mPresenter.loadSuggestionsFromAPI("", true);
   verify(mMockView).showProgressBar(true);
   verify(mMockView).showProgressBar(false);
   verify(mMockView).onSuggestionFetchError(throwable);
}

 

Lastly, we test if our suggestions are saved in the database by counting the number of saved suggestions and asserting that, in testSaveSuggestions method.

@Test
public void testSaveSuggestions() {
   mPresenter.saveSuggestions(queries);
   int count = mDb.where(Query.class).findAll().size();
  // queries is the List that contains the fake suggestions
   assertEquals(queries.size(), count);
}

Resources:

Handling Data Requests in Open Event Organizer Android App

Open Event Organizer is a client side application of Open Event API Server created for event organizers and entry managers. The app maintains a local database and syncs it with the server when required. I will be talking about handling data requests in the app in this blog.

The app uses ReactiveX for all the background tasks including data accessing. When a user requests any data, there are two possible ways the app can perform. The one where app fetches the data directly from the local database maintained and another where it requests data from the server. The app has to decide one of the ways. In the Organizer app, AbstractObservableBuilder class takes care of this. The relevant code is:

final class AbstractObservableBuilder<T> {

   private final IUtilModel utilModel;
   private boolean reload;
   private Observable<T> diskObservable;
   private Observable<T> networkObservable;

   ...
   ...

   @NonNull
   private Callable<Observable<T>> getReloadCallable() {
       return () -> {
           if (reload)
               return Observable.empty();
           else
               return diskObservable
                   .doOnNext(item -> Timber.d("Loaded %s From Disk on Thread %s",
                       item.getClass(), Thread.currentThread().getName()));
       };
   }

   @NonNull
   private Observable<T> getConnectionObservable() {
       if (utilModel.isConnected())
           return networkObservable
               .doOnNext(item -> Timber.d("Loaded %s From Network on Thread %s",
                   item.getClass(), Thread.currentThread().getName()));
       else
           return Observable.error(new Throwable(Constants.NO_NETWORK));
   }

   @NonNull
   private <V> ObservableTransformer<V, V> applySchedulers() {
       return observable -> observable
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread());
   }

   @NonNull
   public Observable<T> build() {
       if (diskObservable == null || networkObservable == null)
           throw new IllegalStateException("Network or Disk observable not provided");

       return Observable
               .defer(getReloadCallable())
               .switchIfEmpty(getConnectionObservable())
               .toList()
               .flatMap(items -> diskObservable.toList())
               .flattenAsObservable(items -> items)
               .compose(applySchedulers());
   }
}

 

DiskObservable is a data request to the local database and networkObservable is a data request to the server. The build function decides which one to use and returns a correct observable accordingly. The class object takes a boolean field reload which is used to decide which observable to subscribe. If reload is true, that means the user wants data from the server, hence networkObservable is returned to subscribe. Also switchIfEmpty in the build method checks whether the data fetched using diskObservable is empty, if found empty it switches the observable to the networkObservable to subscribe.

This class object is used for every data access in the app. For example, this is a code snippet of the gettEvents method in EventRepository class.

@Override
public Observable<Event> getEvents(boolean reload) {
   Observable<Event> diskObservable = Observable.defer(() ->
       databaseRepository.getAllItems(Event.class)
   );

   Observable<Event> networkObservable = Observable.defer(() ->
       eventService.getEvents(JWTUtils.getIdentity(getAuthorization()))
           ...
           ...
           .flatMapIterable(events -> events));

   return new AbstractObservableBuilder<Event>(utilModel)
       .reload(reload)
       .withDiskObservable(diskObservable)
       .withNetworkObservable(networkObservable)
       .build();
}

 

Links:
1. Documentation of ReactiveX API
2. Github repository link of RxJava – Reactive Extension for JVM

Creating Custom Borders for Widgets and Layouts in PSLab Android

User Interface (UI) is one of the most important part of any software development. In PSLab Android App while developing the UI, custom borders are used for various widgets and layouts. This makes the UI look more appealing and widgets and layouts look more highlighted.

In Android, we can do a range of border customization. We can make border rounded, define its thickness and even change its color. Let’s see how to achieve this.

First, go to drawable folder in the tree view on the left size of the Android studio. Then go to new and click on Drawable resource file.

Then a New Resource File dialog box will appear. Type the filename and then click OK.

After this, a new XML file is created. Now we can write the code for creating custom borders. For this, we have to define few elements.

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
....
</shape>

Shape Drawables allows defining background, borders, and gradients for the Views.

<solid android:color="#FFFFFF"/>

Here we are setting the background color of the widget/layout to which the border is applied to.

<stroke android:width="3dip" android:color="#B1BCBE" />

Now we are applying the 3dip width to the border and set its color. This shape requires the <stroke> element to define the width and color of the line.

<corners android:radius="10dip"/>

In order to make the corners of the border round, <corner> element is used to define the radius of the corners. We are taking it to be 10dip.

<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />

The padding is expressed in pixels for the left, top, right and bottom parts of the view. Padding is used to offset the content of the view by a specific number of pixels.

After applying this border on a layout we get the following results.

Similarly making following changes in the element values help us to make border for the Text View

<solid android:color="@android:color/white" />
<stroke android:width="1dip" android:color="#ffcdd2" />
<corners android:radius="2dp"/>

Other examples

Control Activity

Logical Analyzer Activity

Resources

  1. Stack Overflow Solution to How to make a layout with rounded corners?
  2. Youtube Video on How to create a layout with rounded corner borders in Android? by Sylvain Saurel

MVP in Loklak Wok Android using Dagger2

MVP stands for Model-View-Presenter, one of the most popular and commonly used design pattern in android apps. Where “Model” refers to data source, it can be a SharedPreference, Database or data from a Network call. Going by the word, “View” is the user interface and finally “Presenter”, it’s a mediator between model and view. Whatever events occur in a view are passed to presenter and the presenter fetches the data from the model and finally passes it back to the view, where the data is populated in ViewGroups. Now, the main question, why it is so widely used? One of the obvious reason is the simplicity to implement it and it completely separates the business logic, so, easy to write unit-tests. Though it is easy to implement, its implementation requires a lot of boilerplate code, which is one of its downpoints. But, using Dagger2 the boilerplate code can be reduced to a great extent. Let’s see how Dagger2 is used in Loklak Wok Android to implement MVP architecture.

Adding Dagger2 to the project

In app/build.gradle file

dependencies {
   ...
   compile 'com.google.dagger:dagger:2.11'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
}

 

Implementation

First a contract is created which defines the behaviour or say the functionality of View and Presenter. Like showing a progress bar when data is being fetched, or the view when the network request is successful or it failed. The contract should be easy to read and going by the names of the method one should be able to know the functionality of methods. For tweet search suggestions, the contract is defined in SuggestContract interface.

public interface SuggestContract {

   interface View {

       void showProgressBar(boolean show);

       void onSuggestionFetchSuccessful(List<Query> queries);

       void onSuggestionFetchError(Throwable throwable);
   }

   interface Presenter {

       void attachView(View view);

       void createCompositeDisposable();

       void loadSuggestionsFromAPI(String query, boolean showProgressBar);

       void loadSuggestionsFromDatabase();

       void saveSuggestions(List<Query> queries);

       void suggestionQueryChanged(Observable<CharSequence> observable);

       void detachView();
   }
}

 

A SuggestPresenter class is created which implements the SuggestContract.Presenter interface. I will not be explaining how each methods in SuggestPresenter class is implemented as this blog solely deals with implementing MVP. If you are interested you can go through the source code of SuggestPresenter. Similarly, the view i.e. SuggestFragment implements SuggestContract.View interface.

So, till this point we have our presenter and view ready. The presenter needs to access the model and the view requires to have an instance of presenter. One way could be instantiating an instance of model inside presenter and an instance of presenter inside view. But, this way model, view and presenter would be coupled and that defeats our purpose. So, we just INJECT model into presenter and presenter into view using Dagger2. Injecting here means Dagger2 instantiates model and presenter and provides wherever they are requested.

ApplicationModule provides the required dependencies for accessing the “Model” i.e. a Loklak API client and realm database instance. When we want Dagger2 to provide a dependency we create a method annotated with @Provides as providesLoklakAPI and providesRealm.

@Provides
LoklakAPI providesLoklakAPI(Retrofit retrofit) {
   return retrofit.create(LoklakAPI.class);
}

@Provides
Realm providesRealm() {
   return Realm.getDefaultInstance();
}

 

If we look closely providesLoklakAPI method requires a Retrofit instance i.e. a to create an instance of LoklakAPI the required dependency is Retrofit, which is fulfilled by providesRetrofit method. Always remember that whenever a dependency is required, it should not be instantiated at the required place, rather it should be injected by Dagger2.

@Provides
Retrofit providesRetrofit() {
   Gson gson = Utility.getGsonForPrivateVariableClass();
   return new Retrofit.Builder()
           .baseUrl(mBaseUrl)
           .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
           .addConverterFactory(GsonConverterFactory.create(gson))
           .build();
}

 

As the ApplicationModule class provides these dependencies the class is annotated with @Module.

@Module
public class ApplicationModule {

   private String mBaseUrl;

   public ApplicationModule(String baseUrl) {
       this.mBaseUrl = baseUrl;
   }
   
   
   // retrofit, LoklakAPI, realm @Provides methods
}


After preparing the source to provide the dependencies, it’s time we request the dependencies.

Dependencies are requested simply by using @Inject annotation e.g. in the constructor of SuggestPresenter @Inject is used, due to which Dagger2 provides instance of LoklakAPI and Realm for constructing an object of SuggestPresenter.

public class SuggestPresenter implements SuggestContract.Presenter {

   private final Realm mRealm;
   private LoklakAPI mLoklakAPI;
   private SuggestContract.View mView;
   ...

   @Inject
   public SuggestPresenter(LoklakAPI loklakAPI, Realm realm) {
       this.mLoklakAPI = loklakAPI;
       this.mRealm = realm;
       ...
   }
   
   // implementation of methods defined in contract
}


@Inject can be used on the fields also. When @Inject is used with a constructor the class also becomes a dependency provider, this way creating a method with @Provides is not required in a Module class.

Now, it’s time to connect the dependency providers and dependency requesters. This is done by creating a Component interface, here ApplicationComponent. The component interface defines where are the dependencies required. This is only for those cases where dependencies are injected by using @Inject for the member variables. So, we define a method inject with a single parameter of type SuggestFragment, as the Presenter needs to be injected in SuggestFragment.

@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {


   void inject(SuggestFragment suggestFragment);

}

 

The component interface is instantiated in onCreate method of LoklakWokApplication class, so that it is accessible all over the project.

public class LoklakWokApplication extends Application {

   private ApplicationComponent mApplicationComponent;

   @Override
   public void onCreate() {
       super.onCreate();
      ...
       mApplicationComponent = DaggerApplicationComponent.builder()
               .applicationModule(new ApplicationModule(Constants.BASE_URL_LOKLAK))
               .build();
   }

   public ApplicationComponent getApplicationComponent() {
       return mApplicationComponent;
   }
   
   ...
}


NOTE: DaggerApplicationComponent is created after building the project. So, AndroidStudio will show “Cannot resolve symbol …”, thus build the project : Build > Make Module ‘app’.

Finally, in the onCreateView callback of SuggestFragment we call inject method of DaggerApplicationComponent to tell Dagger2 that SuggestFragment is requesting dependencies.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {
...   
   LoklakWokApplication application = (LoklakWokApplication) getActivity().getApplication();
   application.getApplicationComponent().inject(this);
   suggestPresenter.attachView(this);

   return rootView;
}

Resources:

Animations in Loklak Wok Android

Imagine an Activity popping out of nowhere suddenly in front of the user. And even more irritating, the user doesn’t even know whether a button was clicked. Though these are very small animation implementations but these animations enhance the user experience to a new level. This blog deals with the animations in Loklak Wok Android, a peer message harvester of Loklak Server.

Activity transition animation

Activity transition is applied when we move from a current activity to a new activity or just go back to an old activity by pressing back button.

In Loklak Wok Android, when user navigates for search suggestions from TweetHarvestingActivity to SuggestActivity, the new activity i.e. SuggestActivity comes from right side of the screen and the old one i.e. TweetHarvestingActivity leaves the screen through the left side. This is an example of left-right activity transition. For implementing this, two xml files which define the animations are created, enter.xml and exit.xml are created.

<set
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:shareInterpolator="false">

   <translate
       android:duration="500"
       android:fromXDelta="100%"
       android:toXDelta="0%"/>
</set>

 

NOTE: The entering activity comes from right side, that’s why android:fromXDelta parameter is set to 100% and as the activity finally stays at extreme left, android:toXDelta parameter is set to 0%.

As the current activity, in this case TweetHarvestingActivity, leaves the screen from left to the negative of left. So, in exit.xml the android:fromXDelta parameter is set to 0% and android:toXDelta parameter is set to -100%.

Now, that we are done with defining the animations in xml, it’s time we apply the animations, which is really easy. The animations are applied by invoking Activity.overridePendingTransition(enterAnim, exitAnim) just after the startActivity method. For example, in openSuggestActivity

private void openSuggestActivity() {
   Intent intent = new Intent(getActivity(), SuggestActivity.class);
   startActivity(intent);
   getActivity().overridePendingTransition(R.anim.enter, R.anim.exit);
}

 

Touch Selectors

Using touch selectors background color of a button or any clickable can be changed, this way a user can see that the clickable responded to the click. The background is usually light accent color or a lighter shade of the icon present in button.

There are three states involved while a clickable is touched, pressed, activated and selected. And a default state, i.e. the clickable is not clicked. The background color of each state is defined in a xml file like media_button_selector, which is present in drawable directory.

<selector xmlns:android="http://schemas.android.com/apk/res/android">

   <item android:drawable="@color/media_button_touch_selector_backgroud" android:state_pressed="true"/>
   <item android:drawable="@color/media_button_touch_selector_backgroud" android:state_activated="true"/>
   <item android:drawable="@color/media_button_touch_selector_backgroud" android:state_selected="true"/>

   <item android:drawable="@android:color/transparent"/>
</selector>

 

The selector is applied by setting it as the background of a clickable, for example, touch selector applied on Location image button present in fragment_tweet_posting.xml .

<ImageButton
   android:layout_width="40dp"
   android:layout_height="40dp"
   
   android:background="@drawable/media_button_selector" />

 

Notice the change in the background color of the buttons when clicked.

Resources:

Some youtube videos for getting started:

Markdown Support for Experiment Docs in PSLab Android

The PSLab Android App and the PSLab Desktop App come with built-in experiments which include the experiment setups as well as the experiment docs. The experiment docs for PSLab have been written in the Markdown format. So, the markdown support had to be enabled in the PSLab Android App.

There are numerous markdown file renderers for android. The most popular among them is MarkdownView (https://github.com/falnatsheh/MarkdownView) which is an  open-source service.

This blog covers how to enable the support for markdown in apps and use to generate elegant documentation.

Enabling MarkdownView

MarkdownView can be enabled by simply adding a dependency in the build.gradle file

compile 'us.feras.mdv:markdownview:1.1.0'

 

Creating the layout file

The layout file for supporting a markdown file is fairly simple. The inclusion of the above dependency simplifies the things. The view holder for markdown is created and an id is assigned to it.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <br.tiagohm.markdownview.MarkdownView
       android:layout_width="match_parent"
       app:escapeHtml="false"
       android:layout_height="match_parent"
       android:id="@+id/perform_experiment_md" />
</LinearLayout>

 

Loading the markdown file

In order to load the markdown file, a MarkdownView object is created. Since, in the PSLab Android app, markdown files which form the documentation part are a part of the experiments. So, the files are displayed in the documentation fragment of the experiments.

private String mdFile;
private MarkdownView mMarkdownView;

public static ExperimentDocFragment newInstance(String mdFile) {
   ExperimentDocFragment experimentDocFragment = new ExperimentDocFragment();
   experimentDocFragment.mdFile = mdFile;
   return experimentDocFragment;
}

 

The MarkdownView object created is assigned to markdown viewholder of the relevant layout file. Here, the layout file was named experiment_doc_md and the view holder was assigned the id perform_experiment_md. The markdown files were stored in the assets directory of the app and the files were loaded from the there.

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
   View view = inflater.inflate(R.layout.experiment_doc_md, container, false);
   mMarkdownView = (MarkdownView) view.findViewById(R.id.perform_experiment_md);
   mMarkdownView.loadMarkdownFromAsset("capacitance.md");
   return view;
}

 

The available methods in markdown view are

  • loadMarkdown – loads directly from the content in the string 

mMarkdownView.loadMarkdown("**MarkdownView**");

 

  • loadMarkdownFromAsset – loads markdown files located in the assets directory of the app

mMarkdownView.loadMarkdownFromAsset("markdown1.md");

 

  • loadMarkdownFromFile – loads markdown from a file stored in the app not present in the assets directory

mMarkdownView.loadMarkdownFromFile(new File());

 

  • loadMarkdownFromUrl – loads markdown from the specified URL (requires internet connection, as file is loaded from the web)

mMarkdownView.loadMarkdownFromUrl("url");

 

Important points for consideration

  • Avoid using elements of GitHub Flavoured Markdown (GFM) as it is not fully supported. It is better to stick to the traditional markdown style.
  • While adding images in the markdown files, avoid using specific dimensions as the images may not load properly in some cases due to the wide variety of screen sizes in android devices.
  • It is better to store the Markdown files to be loaded in the assets directory of the app and load it from there instead of the other methods mentioned above.

References

  1. A comprehensive markdown tutorial to learn markdown scripting https://www.markdowntutorial.com/
  2. MarkdownView repository on Github by tiagohm https://github.com/tiagohm/MarkdownView
  3. Learn more about Github Flavoured Markdown (GFM) https://guides.github.com/features/mastering-markdown/

Automatic Signing and Publishing of Android Apps from Travis

As I discussed about preparing the apps in Play Store for automatic deployment and Google App Signing in previous blogs, in this blog, I’ll talk about how to use Travis Ci to automatically sign and publish the apps using fastlane, as well as how to upload sensitive information like signing keys and publishing JSON to the Open Source repository. This method will be used to publish the following Android Apps:

Current Project Structure

The example project I have used to set up the process has the following structure:

It’s a normal Android Project with some .travis.yml and some additional bash scripts in scripts folder. The update-apk.sh file is standard app build and repo push file found in FOSSASIA projects. The process used to develop it is documented in previous blogs. First, we’ll see how to upload our keys to the repo after encrypting them.

Encrypting keys using Travis

Travis provides a very nice documentation on encrypting files containing sensitive information, but a crucial information is buried below the page. As you’d normally want to upload two things to the repo – the app signing key, and API JSON file for release manager API of Google Play for Fastlane, you can’t do it separately by using standard file encryption command for travis as it will override the previous encrypted file’s secret. In order to do so, you need to create a tarball of all the files that need to be encrypted and encrypt that tar instead. Along with this, before you need to use the file, you’ll have to decrypt in in the travis build and also uncompress it for use.

So, first install Travis CLI tool and login using travis login (You should have right access to the repo and Travis CI in order to encrypt the files for it)

Then add the signing key and fastlane json in the scripts folder. Let’s assume the names of the files are key.jks and fastlane.json

Then, go to scripts folder and run this command to create a tar of these files:

tar cvf secrets.tar fastlane.json key.jks

 

secrets.tar will be created in the folder. Now, run this command to encrypt the file

travis encrypt-file secrets.tar

 

A new file secrets.tar.enc will be created in the folder. Now delete the original files and secrets tar so they do not get added to the repo by mistake. The output log will show the the command for decryption of the file to be added to the .travis.yml file.

Decrypting keys using Travis

But if we add it there, the keys will be decrypted for each commit on each branch. We want it to happen only for master branch as we only require publishing from that branch. So, we’ll create a bash script prep-key.sh for the task with following content

#!/bin/sh
set -e

export DEPLOY_BRANCH=${DEPLOY_BRANCH:-master}

if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "iamareebjamal/android-test-fastlane" -o "$TRAVIS_BRANCH" != "$DEPLOY_BRANCH" ]; then
    echo "We decrypt key only for pushes to the master branch and not PRs. So, skip."
    exit 0
fi

openssl aes-256-cbc -K $encrypted_4dd7_key -iv $encrypted_4dd7_iv -in ./scripts/secrets.tar.enc -out ./scripts/secrets.tar -d
tar xvf ./scripts/secrets.tar -C scripts/

 

Of course, you’ll have to change the commands and arguments according to your need and repo. Specially, the decryption command keys ID

The script checks if the repo and branch are correct, and the commit is not of a PR, then decrypts the file and extracts them in appropriate directory

Before signing the app, you’ll need to store the keystore password, alias and key password in Travis Environment Variables. Once you have done that, you can proceed to signing the app. I’ll assume the variable names to be $STORE_PASS, $ALIAS and $KEY_PASS respectively

Signing App

Now, come to the part in upload-apk.sh script where you have the unsigned release app built. Let’s assume its name is app-release-unsigned.apk.Then run this command to sign it

cp app-release-unsigned.apk app-release-unaligned.apk
jarsigner -verbose -tsa http://timestamp.comodoca.com/rfc3161 -sigalg SHA1withRSA -digestalg SHA1 -keystore ../scripts/key.jks -storepass $STORE_PASS -keypass $KEY_PASS app-release-unaligned.apk $ALIAS

 

Then run this command to zipalign the app

${ANDROID_HOME}/build-tools/25.0.2/zipalign -v -p 4 app-release-unaligned.apk app-release.apk

 

Remember that the build tools version should be the same as the one specified in .travis.yml

This will create an apk named app-release.apk

Publishing App

This is the easiest step. First install fastlane using this command

gem install fastlane

 

Then run this command to publish the app to alpha channel on Play Store

fastlane supply --apk app-release.apk --track alpha --json_key ../scripts/fastlane.json --package_name com.iamareebjamal.fastlane

 

You can always configure the arguments according to your need. Also notice that you have to provide the package name for Fastlane to know which app to update. This can also be stored as an environment variable.

This is all for this blog, you can read more about travis CLI, fastlane features and signing process in these links below:

Using Universal Image Loader to Display Image on Phimpme Android Application

In Phimpme Android application we needed to load image on the sharing Activity fast so that there won’t be any delay that is visible by a user in the loading of any activity. We used Universal Image Loader to load the image on the sharing Activity to load Image faster.

Getting Universal Image Loader

To get Universal Image Loader in your application go to Gradle(app)-> and then add the following line of code inside dependencies:

dependencies{

compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'

}

Initialising Universal Image Loader and Displaying Image

To display image on using Universal Image Loader we need to convert the image into a URI from a file path:

saveFilePath = getIntent().getStringExtra(EXTRA_OUTPUT);
Uri uri = Uri.fromFile(new File(saveFilePath));

How an image should be displayed

We need to display the image in such a way that it covers the whole image view in the sharing Activity. The image should be zoomed out. The quality of the image should not be distorted or reduced. The image should look as it is. The image should be zoomable so that the user can pinch to zoom in and zoom out. For the image to adjust the whole Image View we set ImageScaleType.EXACTLY_STRETCHED. We will also set cacheInMemory to true and cacheOnDisc to true.  

private void initView() {
   saveFilePath = getIntent().getStringExtra(EXTRA_OUTPUT);
   Uri uri = Uri.fromFile(new File(saveFilePath));
   ImageLoader imageLoader = ((MyApplication)getApplicationContext()).getImageLoader();
   DisplayImageOptions options = new DisplayImageOptions.Builder()
           .cacheOnDisc(true)
           .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
           .cacheInMemory(true)
           .bitmapConfig(Bitmap.Config.RGB_565)
           .build();
   imageLoader.displayImage(uri.toString(), shareImage, options);
}

Image Loader function in MyApplication class:

private void initImageLoader() {
   File cacheDir = com.nostra13.universalimageloader.utils.StorageUtils.getCacheDirectory(this);
   int MAXMEMONRY = (int) (Runtime.getRuntime().maxMemory());
   // System.out.println("dsa-->"+MAXMEMONRY+"   "+(MAXMEMONRY/5));//.memoryCache(new
   // LruMemoryCache(50 * 1024 * 1024))
   DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
           .cacheInMemory(true)
           .cacheOnDisk(true)
           .build();

   ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
           this).memoryCacheExtraOptions(480, 800).defaultDisplayImageOptions(defaultOptions)
           .diskCacheExtraOptions(480, 800, null).threadPoolSize(3)
           .threadPriority(Thread.NORM_PRIORITY - 2)
           .tasksProcessingOrder(QueueProcessingType.FIFO)
           .denyCacheImageMultipleSizesInMemory()
           .memoryCache(new LruMemoryCache(MAXMEMONRY / 5))
           .diskCache(new UnlimitedDiskCache(cacheDir))
           .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
           .imageDownloader(new BaseImageDownloader(this)) // default
           .imageDecoder(new BaseImageDecoder(false)) // default
           .defaultDisplayImageOptions(DisplayImageOptions.createSimple()).build();

   this.imageLoader = ImageLoader.getInstance();
   imageLoader.init(config);
}

Image View in Sharing Activity XML file:

In the Sharing Activity Xml resource, we need to specify the width of the image view and the height of the image view. In Phimpme Android application we are using ImageViewTouch so that we have features like touch to zoom in zoom out. The scale type of the imageView is centerCrop so that image which is loaded is zoomed out and focus is in the center of the image.  

<org.fossasia.phimpme.editor.view.imagezoom.ImageViewTouch
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:id="@+id/share_image"
   android:layout_below="@+id/toolbar"
   android:layout_weight="10"
   android:layout_alignParentStart="true"
   android:scaleType="centerCrop"/>

Conclusion

To load image faster on any ImageView we should use Universal Image Loader. It helps load the activity faster and allows many features as discussed in the blog.

 

Github

Resources

Auto Updating SUSI Android APK and App Preview on appetize.io

This blog will cover the way in which the SUSI Android APK is build automatically after each commit and pushed to “apk” branch in the github repo. Other thing which will be covered is that how the app preview on appetize.io can be updated after each commit. This is basically for the testers who wish to test the SUSI Android App. There are four ways to test the SUSI Android App. One is to simply download the alpha version of the app from the Google PlayStore. Here is the link to the app. Join the alpha testing and report bugs on the github issue tracker of the repo. Other way is to build the app from Android Studio but you may need to set the complete project. If you are looking to contribute in the project, this is the advised way to test the app. The other two ways are explained below.

Auto Building of APK and pushing to “apk” branch

We have written a script which does following steps whenever a PR is merged:

  1. Checks if the commit is of a PR or a commit to repo
  2. If not of PR, configures a user whose github account will be used to push the APKs.
  3. Clones the repo, generates the debug and release APK.
  4. Deletes everything in the apk branch.
  5. Commits and Pushes new changes to apk branch.

This script is written for people or testers who do not have android studio installed in their computer and want to test the app. So, they can directly download the apk from the apk branch and install it in their phone. The APK is always updated after each commit. So, whenever a tester downloads the APK from apk branch, he will always get the latest app.

if [[ $CIRCLE_BRANCH != pull* ]]
then
    git config --global user.name "USERNAME"
    git config --global user.email "EMAIL"

    git clone --quiet --branch=apk https://USERNAME:[email protected]/fossasia/susi_android apk > /dev/null
    ls
    cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/app/build/outputs/apk/app-debug.apk apk/susi-debug.apk
    cp -r ${HOME}/${CIRCLE_PROJECT_REPONAME}/app/build/outputs/apk/app-release-unsigned.apk apk/susi-release.apk
    cd apk

    git checkout --orphan workaround
    git add -A

    git commit -am "[Circle CI] Update Susi Apk"

    git branch -D apk
    git branch -m apk

    git push origin apk --force --quiet > /dev/null
fi

Auto Updating of App Preview on appetize.io

The APKs generated in the above step can now be used to set up the preview of the app on the appetize.io. Appetize.io is an online simulator to run mobile apps ( IOS and Android). Appetize.io provides a nice virtual mobile frame to run native apps with various options like screen size, mobile, OS version, etc. Appetize.io provides some API to update/publish the app. In SUSI, we once uploaded the app on appetize.io and now we are using the API provided by them to update the APK everytime a commit is pushed in the repository.

API information (Derived from official docs of appetize.io):

You may upload a new version of an existing app, or update app settings.

Send an HTTP POST request to

https://[email protected]/v1/apps/PUBLICKEY

Replace APITOKEN with your API token and PUBLICKEY with the public key of the app you’re updating. Your API token must be permissioned to the same account as was used to upload the app. The POST body must be a JSON object. To delete a previously set field, use a value of null.

Optional Fields

  1. url: (string) a publicly accessible link to your .zip, .tar.gz, or .apk file, used to upload a new version of your app.
  2. note: (string) a note for your own purposes, will appear on your management dashboard.

For the url parameter, we have used https://github.com/fossasia/susi_android/raw/apk/susi-debug.apk and note can be anything. We have used Update SUSI Preview.

curl https://[email protected]/v1/apps/mbpprq4xj92c119j7nxdhttjm0 -H 'Content-Type: application/json' -d '{"url":"https://github.com/fossasia/susi_android/raw/apk/susi-debug.apk", "note": "Update SUSI Preview"}'

Summary

This blog covered about how to implement an automatic structure to generate APKs for testing and using that APK to build a preview on websites like appetize.io and then using the APIs provided by them to update the APK after each PR merge in the repo. Check out the resources below to learn more about the topic. So, if you are thinking of contributing to SUSI Android App, this may help you a little in testing the app. But if not, then you can also use the similar technique for your android app as well and ease the life of testers.

Resources

  1. Docs of appetize.io to learn more about the API https://appetize.io/docs
  2. Tutorial on using curl to make API requests https://curl.haxx.se/docs/httpscripting.html
  3. Tutorial on writing basic shell scripts https://ryanstutorials.net/bash-scripting-tutorial/