Testing Asynchronous Code in Open Event Orga App using RxJava

In the last blog post, we saw how to test complex interactions through our apps using stubbed behaviors by Mockito. In this post, I’ll be talking about how to test RxJava components such as Observables. This one will focus on testing complex situations using RxJava as the library itself provides methods to unit test your reactive streams, so that you don’t have to go out of your way to set contraptions like callback captors, and implement your own interfaces as stubs of the original one. The test suite (kind of) provided by RxJava also allows you to test the fate of your stream, like confirming that they got subscribed or an error was thrown; or test an individual emitted item, like its value or with a predicate logic of your own, etc. We have used this heavily in Open Event Orga App (Github Repo) to detect if the app is correctly loading and refreshing resources from the correct source. We also capture certain triggers happening to events like saving of data on reloading so that the database remains in a consistent state. Here, we’ll look at some basic examples and move to some complex ones later. So, let’s start.

public class AttendeeRepositoryTest {

    private AttendeeRepository attendeeRepository;

    @Before
    public void setUp() {
        testDemo = new TestDemo();
    }

    @Test
    public void shouldReturnAttendeeByName() {
        // TODO: Implement test
    }

}

 

This is our basic test class setup with general JUnit settings. We’ll start by writing our tests, the first of which will be to test that we can get an attendee by name. The attendee class is a model class with firstName and lastName. And we will be checking if we get a valid attendee by passing a full name. Note that although we will be talking about the implementation of the code which we are writing tests for, but only in an abstract manner, meaning we won’t be dealing with production code, just the test.

So, as we know that Observables provide a stream of data. But here, we are only concerned with one attendee. Technically, we should be using Single, but for generality, we’ll stick with Observables.

So, a person from the background of JUnit would be tempted to write this code below.

Attendee attendee = attendeeRepository.getByAttendeeName("John Wick")
    .blockingFirst();

assertEquals("John Wick", attendee.getFirstName() + attendee.getLastName());

 

So, what this code is doing is blocking the thread till the first attendee is provided in the stream and then checking that the attendee is actually John Wick.

While this code works, this is not reactive. With the reactive way of testing, not only you can test more complex logic than this with less verbosity, but it naturally provides ways to test other behaviors of Reactive streams such as subscriptions, errors, completions, etc. We’ll only be covering a few. So, let’s see the reactive version of the above test.

attendeeRepository.getByAttendeeName("John Wick")
    .firstElement()
    .test()
    .assertNoErrors()
    .assertValue(attendee -> "John Wick".equals(
        attendee.getFirstName() + attendee.getLastName()
    ));

 

So clean and complete. Just by calling test() on the returned observable, we got this whole suite of testing methods with which, not only did we test the name but also that there are no errors while getting the attendee.

Testing for Network Error on loading of Attendees

OK, so let’s move towards a more realistic test. Suppose that you call this method on AttendeeRepository, and that you can fetch attendees from the network. So first, you want to handle the simplest case, that there should be an error if there is no connection. So, if you have (hopefully) set up your project using abstractions for the model using MVP, then it’ll be a piece of cake to test this. Let’s suppose we have a networkUtil object with a method isConnected.

The NetworkUtil class is a dependency of AttendeeRepository and we have set it up as a mock in our test using Mockito. If this is sounding somewhat unfamiliar, please read my previous article “The Joy of Testing with MVP”.

So, our test will look like this

@Test
public void shouldStreamErrorOnNetworkDown() {
    when(networkUtils.isConnected()).thenReturn(false);
    
    attendeeRepository.getAttendees()
        .test()
        .assertErrorMessage("No Network");
}

 

Note that, if you don’t define the mock object’s behavior like I have here, attendeeRepository will likely throw an NPE as it will be calling isConnected() on an undefined object.

With RxJava, you get a whole lot of methods for each use case. Even for checking errors, you get to assert a particular Throwable, or a predicate defining an operation on the Throwable, or error message as I have shown in this case.

Now, if you run this code, it’ll probably fail. That’s because if you are testing this by offloading the networking task to a different thread by using subscribeOn observeOn methods, the test body may be detached from Main Thread while the requests complete. Furthermore, if testing in an application made for Android, you would have use AndroidSchedulers.mainThread(), but as it is an Android dependency, the test will fail. Well actually, crash. There were some workarounds by creating abstractions for even RxJava schedulers, but RxJava2 provides a very convenient method to override the default schedulers in the form of RxJavaPlugins. Similarly, RxAndroidPlugins is present in the rx-android package. Let’s suppose you have the plan to use Schedulers.io() for asynchronous work and want to get the stream on Android’s Main Thread, meaning you use AndroidSchedulers.mainThread() in the observeOn method. To override these schedulers to Schedulers.trampoline() which queues your tasks and performs them one by one, like the main thread, your setUp will include this:

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

 

And if you are not using isolated tests and need to resume the default scheduler behavior after each test, then you’ll need to add this in your tearDown method

RxJavaPlugins.reset();
RxAndroidPlugins.reset();

Testing for Correct loading of Attendees

Now that we have tested that our Repository is correctly throwing an error when the network is down, let’s test that it correctly loads attendees when the network is connected. For this, we’ll need to mock our EventService to return attendees when queried, since we don’t want our unit tests to actually hit the servers.

So, we’ll need to keep these things in mind:

  • Mock the network until it shows that it is connected to the Internet
  • Mock the EventService to return attendees when queried
  • Call the getter on the attendeeRepository and test that it indeed returned a list of attendees

For these conditions, our test will look like this:

@Test
public void shouldLoadAttendeesSuccessfully() {
    List<Attendee> attendees = Arrays.asList(
        new Attendee(),
        new Attendee(),
        new Attendee()
    );

    when(networkUtils.isConnected()).thenReturn(true);
    when(eventService.getAttendees()).thenReturn(Observable.just(attendees));

    attendeeRepository.getAttendees()
        .test()
        .assertValues(attendees.toArray(new Attendee[attendees.size()]));
}

 

The assertValues function asserts that these values were emitted by the observable. And if you want to be terser, you can even verify that in fact EventService’s getAttendees function was called by

verify(eventService).getAttendees();

 

But the problem in this way is that the getAttendees function returns an observable and just calling it does not necessarily means that it was subscribed, emitting the results, hence we need to test to ensure that it was indeed subscribed. If we call the normal test() function on the observable, it is already subscribed, making the result of testSubscribed always true. In order to test that correctly, let’s look at our final use case.

Testing for saving of Attendees

In the Open Event Orga App, we have strived to create self-sufficient and intelligent classes, thus, our repository is also built this way. It detects that new attendees are loaded from the server and saves them in the database. Now we’d want to test this functionality.

In this test, there is an added dependency of DatabaseRepository for saving the attendees, which we will mock. The conditions for this test will be:

  • Network is connected
  • EventService returns attendees
  • DatabaseRepository mocks the saving of attendees

For DatabaseRepository’s save method, we’ll be returning a Completable, which will notify when the saving of data is completed. The primary purpose of this test will be to assert that this completable is indeed subscribed when the attendee loading is triggered. This will not only ensure that the correct function to save the attendees is called, but also that it is indeed triggered and not just left hanging after the call. So, our test will look like this.

@Test
public void shouldSaveAttendeesInDatabase() {
    List<Attendee> attendees = Arrays.asList(
        new Attendee(),
        new Attendee(),
        new Attendee()
    );

    TestObserver testObserver = TestObserver.create();
    Completable completable = Completable.complete()
        .doOnSubscribe(testObserver::onSubscribe);

    when(networkUtils.isConnected()).thenReturn(true);
    when(databaseRepository.save(attendees)).thenReturn(completable);
    when(eventService.getAttendees()).thenReturn(Observable.just(attendees));

    attendeeRepository.getAttendees()
        .test()
        .assertNoErrors();

    testObserver.assertSubscribed();
}

 

Here, we have created a separate test observable and set it to be subscribed when the Completable is subscribed and we have returned that Completable when the save method is called. In the last, we have asserted that the test observer is indeed subscribed.

You can create more complex use cases and assert subscriptions, errors, the emptiness of a stream and much more, by using the built-in test functionalities of RxJava2. So, that’s all for this blog, you can visit these links for more details on unit testing RxJava

http://fedepaol.github.io/blog/2015/09/13/testing-rxjava-observables-subscriptions/

https://www.infoq.com/articles/Testing-RxJava

Continue ReadingTesting Asynchronous Code in Open Event Orga App using RxJava

Adding Stickers Feature on Phimpme Android App with ViewFlipper and Scrollview

For any image editing application stickers play an important role in editing an image. Phimpme is no different. There can be many different kinds of stickers. As Phimpme is an Open Source application. The user can add their own stickers to be implemented on Phimpme. But this gives a long and non-exhaustive list of stickers that the user has to go through. Sorting the stickers into different categories is one way of doing it. It provides quick access to the desired sticker a user wants to add to her or his photo. In order to achieve this, I am using ViewFlipper and Scrollview.

Categorizing the types of Stickers.

How it is done in Phimpme?

Currently, there are six different categories for stickers. Namely: Facial, Hearts, Objects, Comments, Wishes, Animals. The following code is used to store the category name:

public static final String[] stickerPath = {"stickers/type1", "stickers/type2", "stickers/type3", "stickers/type4", "stickers/type5", "stickers/type6"};
public static final String[] stickerPathName = {"Facial", "Hearts", "Objects", "Comments", "Wishes", "Animals"};

 

We need to populate the sticker fragment with the different categories. This is done by overriding onBinderHolder() function and using ImageHolder to handle onClickListener.

private ImageClick mImageClick = new ImageClick();

Getting the position of the stickerPathName and setting it in stickerFragment.

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String name = stickerPathName[position];
        imageHoler.text.setText(name);
        imageHoler.text.setTag(stickerPath[position]);
        imageHoler.text.setOnClickListener(mImageClick);
    }

 

Adding Stickers to the different categories.

Now that we have different categories, we can assign different types of stickers to specific categories.

We are using RecyclerView.Adapter<ViewHolder>, which implements onCreateViewHolder() method. onCreateViewHolder method is created every time we need a new view, hence RecyclerView comes handy in such situation rather than a normal list.

First, we need to define the xml file where each sticker will be displayed in the form of ImageView. This ImageView has specific maxHeight and maxWidth for a uniform view.

The xml file name is: view_sticker_item.xml

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/img"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:maxHeight="50dp"
    android:maxWidth="50dp"
    android:paddingLeft="5dp"
    android:paddingRight="5dp"
    android:scaleType="centerInside" />


After we set up view_sticker_item.xml, we have to inflate it.

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewtype) {
        View v = null;
        v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.view_sticker_item, parent, false);
        ImageHolder holder = new ImageHolder(v);
        return holder;
    }

 

I have saved all the images in the assets folder and distributed it among type1, type2, type3, type4, type5 and type6.

One thing is to be kept in mind that the images should be in png format. PNG format images are only pixelated on the required areas. So that it does not hamper the background of the image.

Displaying stickers

Displaying sticker are done by overriding onBindViewHolder again. It accepts two parameters: ViewHolder holder and int position. ImageHolder is used to set the image of the sticker and then handling it by onClickListner().

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String path = pathList.get(position);
        ImageLoader.getInstance().displayImage("assets://" + path,
                imageHoler.image, imageOption);
        imageHoler.image.setTag(path);
        imageHoler.image.setOnClickListener(mImageClick);
    }

 

public void addStickerImages(String folderPath) {
        pathList.clear();
        try {
            String[] files = mStirckerFragment.getActivity().getAssets()
                    .list(folderPath);
            for (String name : files) {
                pathList.add(folderPath + File.separator + name);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.notifyDataSetChanged();
    }

 

So sticker fragment on Phimpme looks like this:  

                                        

Link:

https://github.com/fossasia/phimpme-android

Resources

 

Continue ReadingAdding Stickers Feature on Phimpme Android App with ViewFlipper and Scrollview

Adding Twitter Integration with MVP Architecture in Phimpme Android

The Account manager layout part of Phimpme is set. Now we need to start adding different account to make it functional. We first start with twitter. Twitter functionality is integrated with the help of Twitter Kit provided by Twitter itself. We followed the steps provided on the installation guide.

Note: Before Starting this first go to apps.twitter.com and create new app, add the relevant information such as name, description, URL etc.

How twitter works in Phimpme

A dialog box appear when user selected the add account option in Account manager. Select Twitter option from it.

Twitter guides to use custom TwitterLoginButton for sign in. But as we are using a common dialog box. How to initiate login from there?

Using TwitterAuthClient

Twitter Auth Client invoke the Twitter callback and popup the login window. On authorized the correct user it goes inside the onSuccess method and start a Twitter session which helps us to get the information which we want to store in database such as username, access token.

client.authorize(getActivity(), new Callback<TwitterSession>() {
   @Override
   public void success(Result<TwitterSession> result) {

       // Creating twitter session, after user authenticate
       // in twitter popup
       TwitterSession session = TwitterCore.getInstance()
               .getSessionManager().getActiveSession();
       TwitterAuthToken authToken = session.getAuthToken();


       // Writing values in Realm database
       account.setUsername(session.getUserName());
       account.setToken(String.valueOf(session.getAuthToken()));

   }

Working with MVP architecture to show Twitter data to User in a recyclerView

Finally after successful login from Twitter, we also need to show user that you are successfully logged in Phimpme app and also provide a sign out feature so that user can logout from Twitter anytime.

Account manager has a recyclerView which takes data from the database and show it to the User.

Steps:

class AccountContract {
   internal interface View : MvpView{

       /**
        * Setting up the recyclerView. The layout manager, decorator etc.
        */
       fun setUpRecyclerView()

       /**
        * Account Presenter calls this function after taking data from Database Helper Class
        */
       fun setUpAdapter(accountDetails: RealmResults<AccountDatabase>)

       /**
        * Shows the error log
        */
       fun showError()
   }

   internal interface Presenter {

       /**
        * function to load data from database, using Database Helper class
        */
       fun loadFromDatabase()

       /**
        * setting up the recyclerView adapter from here
        */
       fun handleResults(accountDetails: RealmResults<AccountDatabase>)
   }
}

This class clearly show what functions are in View and what are in Presenter. The View interface extended to MvpView which actually holds some common functions such as onComplete()

  • Implement View interface in AccountActivity

class AccountActivity : ThemedActivity(), AccountContract.View

And perform all the action which are happening on the View such as setting up the RecyclerView

override fun setUpRecyclerView() {
   val layoutManager = LinearLayoutManager(this)
   accountsRecyclerView!!.setLayoutManager(layoutManager)
   accountsRecyclerView!!.setAdapter(accountAdapter)
}
  • Main Business Logic should not be in Activity class

That’s why using MVP we have very less number of lines of code in our Main Activity because it separate the work on different zones which help developer to easily work, maintain and other user to contribute.

So like in our case I need to update the RecyclerView adapter by taking data from database. That work should not be in activity that’s why I create a class AccountPresenter and extend this to our Presenter interface in contract class

class AccountPresenter extends BasePresenter<AccountContract.View>
       implements AccountContract.Presenter

I added the function which take care of loading data from database

@Override
public void loadFromDatabase() {
   handleResults(databaseHelper.fetchAccountDetails());
}
  • Always consider the future and keep an eye for future development

Right now I not need to do alot on Database, I just need to pick the whole data and show it on View. But I need to take case of future development in this part as well. There might be more complex operation on Database in future, then it will create complexity in the codebase, if it is not architectured well.

So, I created a DatabaseHelper class which takes care of all the database operations, the advantage of this is, anyone who have to contribute in Database part or debugging the databse need not to search for every activity and scroll lines of code, the work will be in DatabaseHelper for sure.

Added DatabaseHelper in data package

public class DatabaseHelper {

   private Realm realm;

   public DatabaseHelper(Realm realm) {
       this.realm = realm;
   }

   public RealmResults<AccountDatabase> fetchAccountDetails(){
       return realm.where(AccountDatabase.class).findAll();
   }

   public void deleteSignedOutAccount(String accountName){
       final RealmResults<AccountDatabase> deletionQueryResult =  realm.where(AccountDatabase.class)
               .equalTo("name", accountName).findAll();

       realm.executeTransaction(new Realm.Transaction() {
           @Override
           public void execute(Realm realm) {
               deletionQueryResult.deleteAllFromRealm();
           }
       });
   }
}

Flow Diagram:

Browse the Phimpme GitHub Repository for complete illustration.

Resources

  1. Twitter KIt overview : https://dev.twitter.com/twitterkit/android/overview
  2. Login with Twitter: https://dev.twitter.com/twitterkit/android/log-in-with-twitter
  3. MVP Architecture by Ribot: https://github.com/ribot/android-boilerplate

 

Continue ReadingAdding Twitter Integration with MVP Architecture in Phimpme Android

Adding Image Editor in Phimpme Android app

Image editing is a core feature of our Phimpme Android Application. We are using this already existing components Image Editor. Currently our app flow is Gallery → Open images. Now we add an option menu in the top named “Edit” which redirect to the edit Image Activity with that image path. Perform the edit operations there and apply the changed and finally updated the album with new file.

The task is to first get the image path pass to the EditImageActivity and then save the edited image in different folder after user finish with editing.

Below are the high level architecture of how it is achieved with help of ImageEditor.

Steps to integrate Editor:

  • Get the selected Image URI

Firstly, add the permission in manifest file

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Get the local images in your internal or SD Card using Media Store content provider.

Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 

String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.BUCKET_ID,MediaStore.Images.Media.BUCKET_DISPLAY_NAME }; 

Cursor cursor = getContentResolver().query(uri, projection, null, null, null);

After that you can iterate the cursor to end and get details.

  • Create a separate folder where edited images will be saved.

Now after editing we have to place the edited image into a seperate folder with a new name. You can create folder like this:

File aviaryFolder = new File(baseDir, FOLDER_NAME);

Generating new file which will be saved as edited image

public static File getEmptyFile(String name) {

  File folder = FileUtils.createFolders();

  if (folder != null) {

     if (folder.exists()) {

        File file = new File(folder, name);

        return file;

     }

  }

  return null;

}
FileUtils.getEmptyFile("edited"

     + System.currentTimeMillis() + ".png");

 

It will save new image as named edited<timeinmillis> so it will be unique.

  • StartActivityforResult with the image URI and edit activity

So firstly, when user open image and click on edit. We create an empty file using the above method. Getting the image uri and pass it into the editImageActivity. Other than Uri, also pass the output file path.

And startActivityForResult with intent and request code.

You can track the editing done by user and finally save if there is some edit done, like in Phimpme we keep a counter which increased on change of Bitmap.

Update the Album by putting the image Media Store

ContentValues values = new ContentValues(2);

String extensionName = getExtensionName(dstPath);

values.put(MediaStore.Images.Media.MIME_TYPE, "image/" + (TextUtils.isEmpty(extensionName) ? "jpeg" : extensionName));

values.put(MediaStore.Images.Media.DATA, dstPath);

context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

Please visit the whole code here: github.com/fossasia/phimpme-android

Resources

Continue ReadingAdding Image Editor in Phimpme Android app
Read more about the article Using Activities with Bottom navigation view in Phimpme Android
BottonNavigationView

Using Activities with Bottom navigation view in Phimpme Android

Can we use Bottom Navigation View with activities? In the FOSSASIA Phimpme Project we integrated two big projects such as Open Camera and Leafpic. Both are activity based projects and not developed over fragment. The bottom navigation we use will work with fragment.

In Leafpic as well as in Open Camera, there are a lot of Multi level inheritance which runs the app components. The problem faced to continue to work on activities. For theme management using base activity is necessary. The base activity extend to AppcompatActivity. The leafpic code hierarchy is very complex and depend on various activities. Better way is to shift to activities for using Bottom navigation view and  work with activity similar like fragments.

Image source: https://storage.googleapis.com/material-design/

Solution:

This is possible if we copy the bottom navigation item in each of the activity and setting the clicked state of it as per the menu item ID.

Steps to Implement (How I did in Phimpme)

  • Add a Base Activity which takes care of two thing

    • Taking the layout element and setting in the activity.
    • Set up the correct navigation menu item clicked

Function selectBottomNavigationBarItem(int itemId) will take care of what item is currently active. Called this function in onStart(), getNavigationMenuItemId() function will return the menu id and selectBottomNavigagionBarItem update the bottom navigation view.

public abstract class BaseActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener

private void updateNavigationBarState() {
   int actionId = getNavigationMenuItemId();
   selectBottomNavigationBarItem(actionId);
}

void selectBottomNavigationBarItem(int itemId) {
   Menu menu = navigationView.getMenu();
   for (int i = 0, size = menu.size(); i < size; i++) {
       MenuItem item = menu.getItem(i);
       boolean shouldBeChecked = item.getItemId() == itemId;
       if (shouldBeChecked) {
           item.setChecked(true);
           break;
       }
   }
}
  • Add two abstract functions in the BaseActivity for above task
public abstract int getContentViewId();

public abstract int getNavigationMenuItemId();
  • Extend every activity to the Base Activity

For example I created a blank Activity: AccountsActivity and extend to BaseActivity

public class AccountsActivity extends BaseActivity
  • Implement the abstract function in the every activity and set the correct layout and menu item id.

@Override
public int getContentViewId() {
 return R.layout.content_accounts;
}

@Override
public int getNavigationMenuItemId() {
   return R.id.navigation_accounts;
}
  • Remove the setContentView(R.layout.activity_main);
  • Add the onNavigationItemSelected in BaseActivity

@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
   switch (item.getItemId()) {
       case R.id.navigation_home:
    startActivity(new Intent(this, LFMainActivity.class));
           Break;
       case R.id.navigation_camera:
           startActivity(new Intent(this, CameraActivity.class));
           Break;
       case R.id.navigation_accounts:
           startActivity(new Intent(this, AccountsActivity.class));
           Break;
   }
   finish();
   return true;
}

Output:

The transition is as smooth as we use bottom navigation views with Fragment. The bottom navigation also appear on above of the activity.

Source: https://stackoverflow.com/a/42811144/2902518

Resources:

Continue ReadingUsing Activities with Bottom navigation view in Phimpme Android

Phimpme: Merging several Android Projects into One Project

To speed up our development of the version 2 of the Phimpme Android app, we decided to use some existing Open Source libraries and projects such as Open Camera app for Camera features and Leafpic for the photo gallery.

Integrating several big projects into one is a crucial step, and it is not difficult. Here are some points to ponder.

  1. Clone project separately. Build project and note down the features which you want to integrate. Explore the manifest file to know the Launcher, services and other permission app is taking.

I explore the Leafpic app manifest file. Found out its Launcher class, and changed it with the our code. Looked at the permissions app required such as Camera, access fine location, write external storage.

  1. Follow Bottom Up approach while integrating. It makes life easier. By the bottom up approach I mean create a new branch and then start deleting the code which is not required. Always work in branch so that no code lost or messed up.

Not everything is required at the moment. So I first integrate the whole leafpic app and then remove the splash screen. Also noted down the features needs to remove such as drawer, search bar etc.

  1. Remove and Commit, In a big project things are interlinked so removing one feature, actually made you remove most of the code. So my strategy is to focus on one feature and when it removed commit the code. This will help you in a reset hard your code anytime.

Like a lot of times I use reset –hard in my code, because it messed up.

  1. Licensing: One thing which is most important in using open source code is their License. Always follow what is written in their License. This is the way we can give credit to their hard work. There are various types of licenses, three famous are Apache, MIT and GNU General Public License. All have their pros and cons.

Apart of License there are some other condition as well. Like in leafpic which we are using in Phimpme has a condition to inform developers about the project in which we are using leafpic.

  1. Aware of File Duplication, sometimes many files have same name which results into file duplication. So refactor them already in the code.

I refactor the MainActivity in leafpic to LFMainActivity to not to clash with the existing. Resolve package name. This is also one of the tedious task. Android Studio already do a lot of refactoring but some part left.

  • Make sure your manifest file contains correct name.
  • The best way to refactor your package name in xml file is to ctrl+Shift+f in Ubuntu or Edit → Find → Find in path in Android Studio. Then search old package name and replace it with new. You can use alt+ j for multi cursor as well.
  • Clean and rebuild project.

Run app at each step so that you can catch the error at a point. Also use debugger for debugging.

Resources

Continue ReadingPhimpme: Merging several Android Projects into One Project

The Joy of Testing with MVP in Open Event Orga App

Testing applications is hard, and testing Android Applications is harder. The natural way an Android Developer codes is completely untestable. We are born and molded into creating God classes – Activities and perform every bit of logic inside them. Some thought that introduction to Fragments will introduce a little bit of modularity, but we proved otherwise by shifting to God Fragments. When the natural urge of an Android Developer to

  • apply logic,
  • load UI,
  • handle concurrency (hopefully, not using AsyncTask),
  • load data – from the network; disk; and cache,
  • and manage the state of the Activity/Fragment

finally, meets with a new form of revelation that he/she should test his/her application, all of the concepts acquired are shattered in a moment. The person goes to Android Docs to see why he started coding that way and realizes that even the system is flawed – Android Documentation is full of examples promoting God Activities, which they have used to show the use of API for only one reason – brevity. It’s not uncommon for us to steal some code from StackOverflow or Android Docs, but we don’t realize that they are presented there without an application environment or structure, all the required component just glued together to get it functionally complete, so that reader does not have to configure it anymore. Why would an answer from StackOverflow load a barcode scanner using a builder? Or build a ContentProvider to show how to query sorted data from SQLite? Or use a singleton for your provider classes? The simple answer is, they won’t. But this doesn’t mean you shouldn’t too.

The first thought that enters developer’s mind when he gets his hand on a piece of code is to paste it in the correct position and get it to work. There is always this moment of hesitation where conscience rings a bell saying, “What are you doing? Is it the right way to do it? How many lines till you stop overloading this Activity? It’s 2000 lines already”. But it dies as soon as we see the feature is working. And why test something which we have confirmed to work, right? I just wrote this code to show a progress bar while the data loads and hide it when it is done. I can see this working, what will I achieve in painfully writing a test for it, mocking the loading conditions and all. Wrong! Unit tests which test your trivial utils like Date Modification, String Parsing, etc are good and needed but are so trivial that they are hard to go wrong, and if they are, they are easy to fix as they have single usage throughout the app, and it is easy to spot bugs and fix them.

The real problem is testing of your app over dynamic conditions, where you have to emulate them so you can see if your app is making the right decisions. The progress bar working example may work for now, but what if it breaks over refactoring, or you wrote the same code elsewhere and forget to hide the progress bar? Simply copying a well-written test and changing 2-3 class names will fail the build and tell you what’s wrong. A well-contracted app can even contain tests to check that there’ll be no memory leaks. But none of this is possible if everything is jumbled into single Activity with callbacks, loaders, UI handling, business logic, etc. You can’t even think that where to begin. In this post, I will briefly discuss, how to design and test applications using MVP pattern.

MVP to the Rescue

Before moving ahead, I must put a disclaimer saying that MVP is a design pattern which follows a very opinionated implementation. The extent to which you want to refactor your app and the number of abstractions you are willing to do are in your hand. There aren’t any golden rules where your implementation will fail to be called as MVP. Remember, the client doesn’t care about architecture, it’s for you, so whatever makes your life and testing easier, works. Also, I’ll use an axiom related to testing here, “Test until fear turns into boredom”. You can apply the same to your MVP implementation. Testing and design patterns are here to eradicate your fear of failure, not to bore you.

So, in this guide, I’ll be using a use case of a QR Code Scanner Activity built using MVP pattern which we have employed in Open Event Orga Application (Github Repo). Because we are focusing on the test pattern and how to make it easy for us to test our app logic, I have omitted the model part from the MVP equation. The reason being that the possible model in the application would have been the camera loader or barcode initializer and the caveats associated with following this is that both these modules rely heavily on the Android specific view classes, namely, SurfaceView and other lifecycle methods. You could always create your way around it to include them in a separate model, but it won’t help us in writing unit tests for them, because firstly, they aren’t our logic to test, and secondly, they can’t be tested in a unit test (those models would have probably just implemented certain setters and getters).

So, the main purpose of our QR Code Scanner class is to scan a QR code and match it with a list of identifiers, and if there is a match, return it successfully to the caller. In this specific example, the identifiers will the ticket IDs of event attendees and the caller will be event organizer scanning QR codes to check the attendee in. The use case sounds simple, but has several mini use cases and dependencies of itself, which we have to take into our account while designing the View and Presenter class. Let’s discuss them one by one:

App State – Start: Activity starts, loads camera

  • Permission Granted: detects that the app already has Camera Permission,
    • starts scanning
  • Permission Absent: detects that the app doesn’t have Camera Permission,
    • Denied: asks for it, denied, shows error
    • Accepted: asks for it, granted, starts scanning

App State – QR Code Detected: Starts parsing them

  • Attendees are not present: Attendees aren’t present because of some reason, either due to internal error or have not loaded yet
    • stops parsing
  • Attendees are present:
    • QR Code does not match with any attendee : Do nothing
    • QR Code matches with one of the attendee : Send attendee to caller

App State: Camera or containing View is getting destroyed

  • Release Camera

There can be much more internal data flows, but this much is sufficient for our example. So, let’s start defining our contracts using the above knowledge. First, we will design our View. So what should be our strategy? Always think of presenters and views to be mapped in a 1:1 relation. They can have conversations with each other, the difference is that the view is only allowed to talk to the presenter, but presenter may talk to models too. So, the view is going to get all of its information from the presenter and can only take action when presenter tells it to, essentially saying that the view is dumb and passive. The presenter can talk to view and model(s), meaning the collection of information presenter has does not have to come from the view only. In fact, the less dependent presenter has to be on view, the better. The main motive of MVP is to make our logic less dependent on views.

Presenter Contract

In our example, presenter relies on the view to tell it when a certain event happens, they can be lifecycle callbacks, permission grants/denies, camera load/destroy or anything purely related to the Android implementation. So, we generally know from the start what kind of information presenter needs from a View, so let’s design our presenter first.

public interface IScanQRPresenter {

    void attach(long eventId, IScanQRView scanQRView);

    void start();

    void detach();

    void cameraPermissionGranted(boolean granted);

    void onBarcodeDetected(Barcode barcode);

    void onScanStarted();

    void onCameraLoaded();

    void onCameraDestroyed();

}

 

The methods are self-explanatory. Don’t worry if you didn’t get why we defined certain hooks like onScanStarted() or why we didn’t define onScanStopped(). These kinds of details will reveal themselves as you develop your components. You can skip to next section if you don’t want to know why we did it.

Basically, you should define a callback for events which are not reliable to be synchronized. What? Let me explain. Let’s say you got your camera permission and requested the view to start scanning, but you have to wait till the camera is loaded as it is not a synchronous call (and it shouldn’t be or your main thread will block). So, instead of that, our presenter will request the camera to load, go in an idle state, wait for the onCameraLoaded() call, and then request the scan to start, and since it is also not a synchronous work, it will go into idle state again and wait for onScanStarted() and then further its work. Whenever the camera is destroyed, the onCameraDestroyed() callback will be called and we will stop the scanning, and since there is nothing to be done after that, we won’t wait till scanning has stopped, thus dropping the need of onScanStopped() callback.

View Contract

View contract will come naturally to you once you have understood the data flow. It will contain the commands presenter will issue on the view and also, the requests for data that view holds. So, this will be our view.

public interface IScanQRView {

    boolean hasCameraPermission();

    void requestCameraPermission();

    void showPermissionError(String error);

    void onScannedAttendee(Attendee attendee);

    void showBarcodePanel(boolean show);

    void showBarcodeData(@NonNull String data);

    void showProgressBar(boolean show);

    void loadCamera();

    void startScan();

    void stopScan();

}

 

Almost all commands are straightforward but let me quickly explain showBarcodePanel(boolean) and showBarcodeData(String). They are used to display the currently visible barcode data to the user.

So, with our implementations set and data flow in place, let’s start writing tests for the feature. Yes, we’ll write tests without implementation and then you’ll see how easy it will be to write your views and presenters with only one goal to mind, to make the tests pass. If your tests are written correctly and cover everything, you should feel confident that your app will work without even seeing the actual implementation, because that is what tests are for. And by making passive and dumb views, imagine how light your instrumentation tests will be. Just check that the individual methods in view implementation are working as expected and you are done! No need to test logic or complex interactions, etc because you have got it covered in the unit tests themselves. This is not only a benefit of data flow tests but also a best practice. You always want to follow DRY, Don’t Repeat Yourself, even while testing.

Tests

So, we will start writing our tests now and you’ll realize how easy it is and all the hard work of abstraction and designing will pay off.

Attach Tests

So, firstly, we will test if the presenter calls appropriate methods when it is attached

@Test
public void shouldLoadAttendeesAutomatically() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    verify(eventRepository).getAttendees(eventId, false);
}

@Test
public void shouldLoadCameraAutomatically() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    verify(scanQRView).loadCamera();
}

 

Here, we are using Mockito to mock our EventDataRepository to return locally defined attendees instead of doing an actual network call. Then we are calling attach on presenter in each method and then we verify in the first test that the presenter is calling getAttendees on the EventDataRepository, and in the second test that it is requesting the view to load the camera.

Note that in the implementation of attach function, both loading of Camera and attendee loading will take place, but it is best practice to test them separately so that when a test fails, we know why it did

Detach Tests

@Test
public void shouldDetachViewOnStop() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));

    scanQRPresenter.start();

    assertNotNull(scanQRPresenter.getView());

    scanQRPresenter.detach();

    assertNull(scanQRPresenter.getView());
}

@Test
public void shouldNotAccessViewAfterDetach() {
    scanQRPresenter.detach();

    scanQRPresenter.start();
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(false);
    scanQRPresenter.cameraPermissionGranted(true);
    scanQRPresenter.onScanStarted();
    scanQRPresenter.onBarcodeDetected(barcode2);
    scanQRPresenter.onCameraDestroyed();

    verifyZeroInteractions(scanQRView);
}

 

In detach tests, we are verifying that after attaching and view not being null, the detach method call makes the presenter leave the reference to the view making it null. And in the second test, we do all possible interactions after detaching and confirm that no call whatsoever was made on the view. Tests like these enforce to avoid memory leaks and check for any NullPointerExceptions that may happen after the view was made null.

Note: This does not mean that memory leaks will not happen if this test passes. You can cause memory leaks by giving the view reference to any long living object, not just the presenter. This just ensures that presenter will not hold the view reference after detach and won’t reference to the null view in future.

Permission Tests

@Test
public void shouldStartScanOnCameraLoadedIfPermissionPresent() {
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.onCameraLoaded();

    verify(scanQRView).startScan();
}

@Test
public void shouldAskPermissionOnCameraLoadedIfPermissionsAbsent() {
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.onCameraLoaded();

    verify(scanQRView).requestCameraPermission();
}

@Test
public void shouldStartScanningOnPermissionGranted() {
    scanQRPresenter.cameraPermissionGranted(true);

    verify(scanQRView).startScan();
}

@Test
public void shouldShowErrorOnPermissionDenied() {
    scanQRPresenter.cameraPermissionGranted(false);

    verify(scanQRView).showPermissionError(matches("(.*permission.*denied.*)|(.*denied.*permission.*)"));
}

The permission tests are straightforward:

Implicit Permission Handling

  1. If the view already has the camera permission and camera has loaded, start scanning
  2. If the view does not have camera permission and the camera has loaded, request the permission

Request Handling

  1. If the request was granted, start scanning
  2. If the permission was denied, show the permission error. Here, I have used regex to match that the error message contains permission and denied words, you can use anyString() from Mockito for more flexibility or a specific message for more tight testing

Camera Destroy Test

@Test
public void shouldStopScanOnCameraDestroyed() {
    scanQRPresenter.onCameraDestroyed();

    verify(scanQRView).stopScan();
}

Pretty simple, stop the scan on destruction of camera

Flow Tests

You can also test that the callback flow happens in order so that not only the unit tests work but also the implementation logic is correct.

/**
 * Checks that the flow of commands happen in order
 */
@Test
public void shouldFollowFlowOnImplicitPermissionGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).loadCamera();
    scanQRPresenter.onCameraLoaded();
    inOrder.verify(scanQRView).startScan();
}

@Test
public void shouldShowProgressInBetweenImplicitPermissionGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(true);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.onScanStarted();
    inOrder.verify(scanQRView).showProgressBar(false);
}

Here, we are verifying two things, first that if we have the request granted implicitly, we load the camera and start the scan in order. This test isn’t that useful as we already tested that loading of the camera is done on attach and scan is started when the camera is loaded. In fact, this is an example of breaking the DRY rule. Even though it doesn’t hurt to include this, it also doesn’t help as it does not cover anything that hasn’t already been tested.

The second test is important and tests that progress bar is correctly shown and hidden after certain communications have taken place and the scan has started. Similarly, we can also test the progress bar behavior over all of the possible combinations of cases that can happen. The code snippets below show the tests:

@Test
public void shouldShowProgressInBetweenImplicitPermissionDenyRequestGrant() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(true);
    scanQRPresenter.onScanStarted();
    inOrder.verify(scanQRView).showProgressBar(false);
}

@Test
public void shouldShowProgressInBetweenImplicitPermissionDenyRequestDeny() {
    when(eventRepository.getAttendees(eventId, false))
        .thenReturn(Observable.fromIterable(attendees));
    when(scanQRView.hasCameraPermission()).thenReturn(false);

    scanQRPresenter.start();

    InOrder inOrder = inOrder(scanQRView);
    inOrder.verify(scanQRView).showProgressBar(true);
    scanQRPresenter.onCameraLoaded();
    scanQRPresenter.cameraPermissionGranted(false);
    inOrder.verify(scanQRView).showProgressBar(false);
}

QR Code Detection Tests

@Test
public void shouldNotSendAnyBarcodeIfAttendeesAreNull() {
    sendNullInterleaved();

    verify(scanQRView, never()).onScannedAttendee(any(Attendee.class));
}

@Test
public void shouldNotSendAttendeeOnWrongBarcodeDetection() {
    scanQRPresenter.setAttendees(attendees);
    sendNullInterleaved();

    verify(scanQRView, never()).onScannedAttendee(any(Attendee.class));
}

@Test
public void shouldSendAttendeeOnCorrectBarcodeDetection() {
    // Somehow the setting in setUp is not working, a workaround till fix is found
    RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());

    scanQRPresenter.setAttendees(attendees);

    barcode1.displayValue = "test4-91";
    scanQRPresenter.onBarcodeDetected(barcode1);

    verify(scanQRView).onScannedAttendee(attendees.get(3));
}

 

Lastly, the core tests of our presenter. To explain the tests, let me show you the sendNullInterleaved() method

private void sendNullInterleaved() {
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode2);
    sendBarcodeBurst(barcode2);
    sendBarcodeBurst(null);
    sendBarcodeBurst(barcode1);
    sendBarcodeBurst(null);
}

 

So what it basically does is send some barcodes interleaved with null (no barcode detected) to the presenter using the onBarcodeDetected method to emulate the real camera sending barcode values with sometimes sending null whenever no barcode is in view.

The first test simply checks that no attendee is sent if the attendee list is null. Seems pretty obvious. The second one checks that if barcode does not match with any attendee’s identifier, it should not send any attendee as well. It does this by setting an arbitrary list of attendees with different identifiers and sending non-matching barcodes to the presenter. Lastly, the successful test, where a single matching barcode is sent to the presenter and it should send that particular attendee with matching identifier.

Phew! Quite a lot of tests and we are done. The tests were not very large and mostly self-explanatory. Now, you just have to implement the view and presenter methods till all the tests light up to be green and you are done! You have created a feature using MVP design and implemented it using test driven development.

So start testing and get a seal of reliability and confidence about your code and the satisfaction of seeing the green bar fill up.

Continue ReadingThe Joy of Testing with MVP in Open Event Orga App

Remove redundancy of images while uploading it from the Phimpme photo app

Uploading images is an important feature for the Phimpme Photo App. While uploading images in the Phimpme application, there was a high probability that the user uploaded redundant images since there was no check on the upload. Therefore to tackle this problem, images should be hashed so that no two same images gets uploaded. To generate a hash of the file, the md5 algorithm and the sha1 algorithm is used.

What is MD5 algorithm?

The MD5 hashing algorithm is a one-way cryptographic function that accepts a message of any length as input and returns as output a fixed-length digest value to be used for authenticating the original message. It produces a 128 bit hash value. 

WhatIs.com

What is SHA1 algorithm?

SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function designed by the United States National Security Agency and is a U.S. Federal Information Processing Standard published by the United States NIST. SHA-1 produces a 160-bit (20-byte) hash value known as a message digest. A SHA-1 hash value is typically rendered as a hexadecimal number, 40 digits long.  

 – Wikipedia

Reason for using MD5 + SHA1 instead of SHA256

  1. The file becomes highly secured while maintaining its integrity.
  2. The algorithm MD5 + SHA1 is faster in hashing than SHA256.

Implementation

Create a separate Utils class named FileUtils.java. The class contains the static function to get the hash of any file in general. The message digest class is being used in both the algorithms.

The hash is the combination of the md5 hash string along with the SHA1 hash string. The message digest takes up algorithm as its argument to specify which hashing function would be used. The fileinputstream takes up the input file for initialization and the message digest is updated with the data bytes of the file. The StringBuffer class is then used to generate the fixed length hexadecimal value from the message digest since the string is going to be mutable in nature. The string value of the generated string buffer object is returned.

public class FileUtils {

//The utility method to get the hash of the file
 public static String getHash(final File file) throws NoSuchAlgorithmException, IOException {

  return md5(file) + "_" + sha1(file);
 }

//The md5 alogorithm
 public static String md5(final File file) throws NoSuchAlgorithmException, IOException {

  MessageDigest md = MessageDigest.getInstance("MD5");
  FileInputStream fis = new FileInputStream(file);
  byte[] dataBytes = new byte[1024];
  int nread = 0;
  while ((nread = fis.read(dataBytes)) != -1) {
   md.update(dataBytes, 0, nread);
  }

  //convert the byte to hex format
  StringBuffer sb = new StringBuffer("");
  for (byte mdbyte: md.digest()) {
   sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1));
  }

  fis.close();
  md.reset();
  return sb.toString();
 }

//The SHA1 algorithm
 public static String sha1(final File file) throws NoSuchAlgorithmException, IOException {

  MessageDigest md = MessageDigest.getInstance("SHA1");
  FileInputStream fis = new FileInputStream(file);
  byte[] dataBytes = new byte[1024];
  int nread = 0;
  while ((nread = fis.read(dataBytes)) != -1) {
   md.update(dataBytes, 0, nread);
  }

  //convert the byte to hex format
  StringBuffer sb = new StringBuffer("");
  for (byte mdbyte: md.digest()) {
   sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1));
  }

  fis.close();
  md.reset();
  return sb.toString();
 }
}

Conclusion

Physical memory is limited. Hence it is of utmost importance that a particular file is identified using a unique identifier in terms of a fixed hash value. Not only will it be beneficial for optimum space utilization, it will also be useful to track it if necessary.

To learn more about the hash alogorithms, visit – https://www.tutorialspoint.com/cryptography/cryptography_hash_functions.htm  

 

Continue ReadingRemove redundancy of images while uploading it from the Phimpme photo app

Use of SwipeRefreshLayout in Phimpme

In an image application, the first thing we want is to get a list of all images from the users device at the start of the application in the splash screen. But what if the number of images in the device is altered by deleting or copying any other images to the device when the application is running. This is where the swipe refresh layout helps us to obtain the real-time list of images from our device by just swiping vertically downwards.

In the Phimpme application, we have made use of the swipe refresh layout to update the image according to the latest data. In this post, I will be discussing how we achieved this in the Phimpme application with the help of some code examples.

Steps to implement the SwipeRefreshLayout:

  1. The first thing we need to do is add the support library to our project build.gradle file.
dependencies {
 compile 'com.android.support:recyclerview-v7:25.3.1'
 compile 'com.android.support:support-v4:25.3.1'
}

2. Now add the SwipeRefreshLayout in Activity.xml file where you want to implement the SwipeRefreshLayout with the recyclerView and the child view should match the parent layout. This can be done using the line of code below:

<android.support.v4.widget.SwipeRefreshLayout
   android:id="@+id/swipeRefreshLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:paddingBottom="@dimen/height_bottombar" > <android.support.v7.widget.RecyclerView
   android:id="@+id/grid_albums"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_gravity="center_horizontal"
   android:scrollbarThumbVertical="@drawable/ic_scrollbar"
   android:scrollbars="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>

3. Now we have to implement the onRefreshListener which handles the refresh operation when a vertical swipe is performed.

Activity.java

SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.sr);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
   @Override
   public void onRefresh() {
   }

});

 

Now we can do anything in onRefresh method which we want to do when user swipes vertically.

For example, we want to refresh the recyclerView with latest images and display the content so this should be done in a background thread.

So we will use AsyncTask to update the data.

  @Override
 public void onRefresh() {
new AsyncTask().execute(); //  This will execute the AsyncTask
   }
});

AsyncTask.java

private class AsyncTask extends AsyncTask<Void, Integer, Void> {


   @Override

   protected void onPreExecute() {

       swipeRefreshLayout.setRefreshing(true);

       super.onPreExecute();

   }


   @Override

   protected Void doInBackground(Void... arg0) {

       // refresh the content of recyclerView here

       return null;

   }


   @Override

   protected void onPostExecute(Void result) {

    swipeRefreshLayout.setRefreshing(false);

   }

}

 

When the user swipes up vertically the progress bar is shown to screen and which can be done by swipeRefreshLayout.setRefreshing(true) in onPreExecute() method.

In doInBackground() method we have to load the content of RecyclerView through an adapter.

Once the data is loaded and set to recyclerView onPostExecute() method is called and now we have to dismiss the loading progress bar by  

  swipeRefreshLayout.setRefreshing(false);

This is how SwipeRefreshLayout works.

The above code helps us to reload the album content in the gallery.

For more details refer here in the Phimpme project for SwipeRefreshLayout.

https://github.com/fossasia/phimpme-android/blob/development/app/src/main/java/vn/mbm/phimp/me/leafpic/activities/LFMainActivity.java

Resources :

https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html

https://developer.android.com/training/swipe/add-swipe-interface.html

Continue ReadingUse of SwipeRefreshLayout in Phimpme

Adding Global Search and Extending Bookmark Views in Open Event Android

When we design an application it is essential that the design and feature set enables the user to find all relevant information she or he is looking for. In the first versions of the Open Event Android App it was difficult to find the Sessions and Speakers related to a certain Track. It was only possible to search for them individually. The user also could not view bookmarks on the Main Page but had to go to a separate tab to view them. These were some capabilities I wanted to add to the app.

In this post I will outline the concepts and advantages of a Global Search and a Home Screen in the app. I took inspiration from the Google I/O 2017 App  that had these features already. And, I am demonstrating how I added a Home Screen which also enabled users to view their bookmarks on the Home Screen itself.

Global Search v/s Local Search

Local Search
Global Search

 

 

 

 

 

 

 

 

 

If we observe clearly in the above images we can see there exists a stark difference in the capabilities of each search.
See how in the Local Search we are just able to search within the Tracks section and not anything else.
This is fixed in the Global Search page which exists along with the new home screen.
As all the results that a user might need are obtained from a single search, it improves the overall user-experience of the app. Also a noticeable feature that was missing in the current iteration of the application was that a user had to go to a separate tab to view his/her bookmarks. It would be better for the app to have a home page detailing all the Event’s/Conference’s details as well as display user bookmarks on the homepage.

New Home

Home screen
Home screen with Bookmarks

 

 

 

 

 

 

 

 

 

Home screen with Bookmarks               
Home screen Demo

 

 

 

 

 

 

 

 

 

The above posted images/gifs indicate the functioning and the UI/UX of the new Homescreen within the app.
Currently I am working to further improve the way the Bookmarks are displayed.
The new home screen provides the user with the event details i.e FOSSASIA 2017 in this case. This would be different for each conference/event and the data is fetched from the open-event-orga server(the first part of the project) if it doesn’t already exist in the JSON files provided in the assets folder of the application. All the event information is being populated by the JSON files provided in the assets folder in the app directory structure.

  • config.json
  • sponsors.json
  • microlocations.json
  • event.json(this stores the information that we see on the home screen)
  • sessions.json
  • speakers.json
  • track.json

All the file names are descriptive enough to denote what do all of them store.I hope that I have put forward why the addition of a New Home with Bookmarks along with the Global Search feature was a neat addition to the app.

Link to PR for this feature : https://github.com/fossasia/open-event-android/pull/1565

Resources

 

 

Continue ReadingAdding Global Search and Extending Bookmark Views in Open Event Android