Show Option to choose WiFi for Smart Speaker Connection in SUSI.AI Android APP

SUSI.AI android app has the functionality to detect the available WiFi networks and among them check for the hotspot named “SUSI.AI”. Now, this process is required so that the app can connect to the smart speaker and send the WiFi credentials, the authentication credentials and the configuration data to the smart speaker.

After on clicking the “SET UP” button on the available SUSI.AI hotspot as shown in the image below,

The app needs to make API requests where the app will send the data to the speaker, the first API that needs to be hit is for the WiFi credentials. Once the “SET UP” button is clicked the app shows a message “Connecting to your device” with a loader as in the image below :

Now during this step the code to detect the available WiFi networks is run again and the list of the available networks is sent from the DeviceConnectPresenter.kt to the DeviceConnectFragment.kt from the function defined in the presenter as follows :

override fun availableWifi(list: List<ScanResult>) {
  connections = ArrayList<String>()
  for (i in list.indices) {
      connections.add(list[i].SSID)
  }
  if (!list.isEmpty()) {
      deviceConnectView?.setupWiFiAdapter(connections)
  } else {
      deviceConnectView?.onDeviceConnectionError(utilModel.getString(R.string.no_device_found), utilModel.getString(R.string.setup_tut))
  }
}

Now to show the list of available WiFi networks a new ViewHolder had to made which contained a textview and an imageview. The viewholder file named WifiViewHolder.java is responsible for this and is used along with the DeviceAdapter only.

The interesting thing to see is that the DeviceAdapter already inflates the ViewItems of the type DeviceViewHolder. Instead of making a new adapter for the WifiViewHolder view item, I wrote some smart code that handles both the viewholders with the same adapter i.e DeviceAdapter. Let’s now see how this was handled,

In the DeviceAdapter a private variable named viewCode of type integer was made and it would be responsible to segregate the two viewholders using an if command. Here is code below that shows how using viewcode variable allows us to choose and inflate a viewholder among the two we have in onCreateViewHolder() method :

if (viewCode == 1) {
  View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_layout, parent, false);
  return new DeviceViewHolder(v, (DeviceConnectPresenter) devicePresenter);
} else {
  View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_wifi_item, parent, false);
  return new WifiViewHolder(v, (DeviceConnectPresenter) devicePresenter);
}

Now, when the viewholder of type WifiViewHolder is inflated the app displays a list of available wifi networks and on clicking any of the Wifi items the app asks the user to enter the credentials of the WiFi network. Below is how the app looks when the available wifi networks are loaded and displayed and also when we click on any of the item in the list.

References :

  1. Android manipulating Wifi networks using WiFiManager : https://medium.com/@josiassena/android-manipulating-wifi-using-the-wifimanager-9af77cb04c6a
  2. Kotlin broadcast intents and receivers : https://www.techotopia.com/index.php/Kotlin_Android_Broadcast_Intents_and_Broadcast_Receivers
  3. Android Viewholder pattern : https://www.javacodegeeks.com/2013/09/android-viewholder-pattern-example.html
Continue Reading

Option to Rename an Image in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to rename an image. So in this blog post, I will be discussing how we achieved the functionality to rename an image.

Step 1

First, we need to add an option in the overflow menu to rename the image being viewed. The option to rename an image has been added by implementing the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/rename_photo”
  app:showAsAction=“never”
  android:title=“@string/Rename”/>

Step 2

Now after the user chooses the option to rename any image from the overflow menu, an alert dialog with an edittext would be displayed to the user with the old name and provide the user to add a new name for the photo. The dialog box with edittext has been implemented with the following lines of XML code.

<android.support.v7.widget.CardView
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/description_dialog_card”>
  <ScrollView
      android:layout_width=“match_parent”
      android:layout_height=“match_parent”>
      <LinearLayout
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:orientation=“vertical”>
          <TextView
              android:id=“@+id/description_dialog_title”
              android:layout_width=“match_parent”
              android:textColor=“@color/md_dark_primary_text”
              android:layout_height=“wrap_content”
              android:background=“@color/md_dark_appbar”
              android:padding=“24dp”
              android:text=“@string/type_description”
              android:textSize=“18sp”
              android:textStyle=“bold” />
          <LinearLayout
              android:id=“@+id/rl_description_dialog”
              android:layout_width=“match_parent”
              android:layout_height=“wrap_content”
              android:orientation=“horizontal”
              android:padding=“15dp”>
              <EditText
                  android:id=“@+id/description_edittxt”
                  android:layout_width=“fill_parent”
                  android:layout_height=“wrap_content”
                  android:padding=“15dp”
                  android:hint=“@string/description_hint”
                  android:textColorHint=“@color/grey”
                  android:layout_margin=“20dp”
                  android:gravity=“top|left”
                  android:inputType=“textCapSentences|textMultiLine”
                  android:maxLength=“2000”
                  android:maxLines=“4”
                  android:selectAllOnFocus=“true”/>
              <ImageButton
                  android:layout_width=“@dimen/mic_image”
                  android:layout_height=“@dimen/mic_image”
                  android:layout_alignRight=“@+id/description_edittxt”
                  app2:srcCompat=“@drawable/ic_mic_black”
                  android:layout_gravity=“center”
                  android:background=“@color/transparent”
                  android:paddingEnd=“10dp”
                  android:paddingTop=“12dp”
                  android:id=“@+id/voice_input”/>
          </LinearLayout>
      </LinearLayout>
  </ScrollView>
</android.support.v7.widget.CardView>

The screenshot displaying the dialog with edittext is provided below.

Step 3

Now after retrieving the new name entered by the user for the photo we would extract the extension of the old name which can be .jpg, .png etc. Thereafter we’d need to create a new File object passing in the new path of the image(the path folder remains the same only the image name gets changed to a new one) as the constructor parameter. Now using the renameTo method of the File class the old image file can be renamed. However, with this rename operation the image reference would not be automatically updated in the MediaStore database. So we’d need to delete the old file path from the Android system MediaStore database which keeps a URI reference to all the media files present on the device. At last, we’d need to invoke the MediaScanner class to scan all the media files so that the new file path of the renamed image is scanned and is picked up by the MediaStore database. This can be done with the help of an action intent to initiate the media scan action. The code changes implemented to perform the above-mentioned operation is given below.

public void onClick(View dialog) {
      if (editTextNewName.length() != 0) {
          int index = file.getPath().lastIndexOf(“/”);
          String path = file.getPath().substring(0, index);
          File newname = new File(path + “/” + editTextNewName.getText().toString() + “.” +
                  imageextension);
          if(file.renameTo(newname)){
              ContentResolver resolver = getApplicationContext().getContentResolver();
              resolver.delete(
                      MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.DATA +
                              “=?”, new String[] { file.getAbsolutePath() });
              Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
              intent.setData(Uri.fromFile(newname));
              getApplicationContext().sendBroadcast(intent);
          }
          if(!allPhotoMode){
              int a = getAlbum().getCurrentMediaIndex();
              getAlbum().getMedia(a).setPath(newname.getPath());
          }else {
              listAll.get(current_image_pos).setPath(newname.getPath());
          }
          renameDialog.dismiss();
          SnackBarHandler.showWithBottomMargin(parentView, getString(R.string.rename_succes), navigationView
                  .getHeight());
      } else {
          SnackBarHandler.showWithBottomMargin(parentView, getString(R.string.insert_something),
                  navigationView.getHeight());
          editTextNewName.requestFocus();
      }
  }
});

This is how we have implemented the functionality to rename an image in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer documentation-https://developer.android.com/reference/java/io/File

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Renaming a file in java – http://stacktips.com/tutorials/java/how-to-delete-and-rename-a-file-in-java

 

Continue Reading

Option to delete albums from storage in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to delete unwanted albums permanently from the storage. So in this post, I will be discussing how we implemented the functionality to delete albums.

Step 1

First, we need to provide an option in the overflow menu to delete album/albums when the user long-selects album/albums or the user is viewing photos inside a particular album. The option in the overflow menu can be added by integrating following lines of code in the menu_albums.xml file(this file contains the XML code for overflow menu options provided for albums).

 <item
android:id=”@+id/delete_action”
android:title=”@string/delete”
app:showAsAction=”ifRoom” />

Step 2

Now when the user opts to delete some selected albums, we need to retrieve user’s choice of albums to delete and store it in an ArrayList<Album> to keep track of the albums and for further use. We would here also keep track of all the albums displayed currently to the user through another ArrayList<Album> named displayAlbums. Thereafter deleteSelectedAlbums method of class HandlingAlbums will be invoked passing in the activity context as the parameter. Now in this method we’d iterate through the ArrayList<Album>(ArrayList used to store user’s choice of albums to delete) and in turn invoke another method deleteAlbum with each iteration, passing in the album and context as the parameters. The deleteAlbum method would return a boolean value that will indicate whether or not the particular album was successfully deleted. If the boolean value returned is true, indicating that the particular album and its contents have been permanently deleted from the device storage, then we would delete that particular album object from the displayAlbums list and the AlbumsAdapter would be notified to display the remaining set of albums by passing in the modified displayAlbums list. The code snippet used to implement the method deleteSelectedAlbums and deleteAlbum is provided below.

public boolean deleteSelectedAlbums(Context context) {
boolean success = true;

for (Album selectedAlbum : selectedAlbums) {
  int index = dispAlbums.indexOf(selectedAlbum);
  if(deleteAlbum(selectedAlbum, context))
    dispAlbums.remove(index);
  else success = false;
}
return success;
}
public boolean deleteAlbum(Album album, Context context) {
return ContentHelper.deleteFilesInFolder(context, new File(album.getPath()));
}

Step 3

In the previous step, we were invoking a function deleteAlbum which would return a boolean variable denoting whether the album was deleted permanently or not. Therefore in this step, I’d be discussing the working of the deleteAlbum method. So the deleteAlbum method was being invoked passing in the album to delete and the context as the parameters. This method, in turn invokes the deleteFilesInFolder method of the ContentHelper class passing in the context and a File object created from the album path(to be deleted). This method returns a boolean variable indicating the success or failure of the operation and this variable is in turn returned by the deleteAlbum method. The code representing the function invoke has been provided above. The code snippet representing the deleteFilesInFolder method is provided below.

public static boolean deleteFilesInFolder(Context context, @NonNull final File folder) {
 boolean totalSuccess = true;

 String[] children = folder.list();
 if (children != null) {
    for (String child : children) {
       File file = new File(folder, child);
       if (!file.isDirectory()) {
          boolean success = deleteFile(context, file);
          if (!success) {
             Log.w(TAG, “Failed to delete file” + child);
             totalSuccess = false;
          }
       }
    }
 }
 return totalSuccess;
}

Now inside the deleteFilesInFolder method first all the contents of the file are stored in a String[] array child. Thereafter by iterating through the child array, every time a method deleteFile is invoked passing in an item from the child array(an image path) and the context as parameters, which returns a boolean variable indicating if the particular image content is deleted or not.

Now depending on which Android version the device is running, there are two methods used to delete the image file permanently from the storage.

If the device is running on version LOLLIPOP and above, the Android Storage Access Framework has been used to delete the image file, and if the device is running version KITKAT, the MediaStore content provider is used to delete the file from device’s storage. The code snippet implementing the deleteFile method is provided below.

public static boolean deleteFile(Context context, @NonNull final File file) {
 boolean success = file.delete();

 // Try with Storage Access Framework.
 if (!success && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    DocumentFile document = getDocumentFile(context, file, false, false);
    success = document != null && document.delete();
 }

 // Try the Kitkat workaround.
 if (!success && Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
    ContentResolver resolver = context.getContentResolver();

    try {
       Uri uri = null;//MediaStoreUtil.getUriFromFile(file.getAbsolutePath());
       if (uri != null) {
          resolver.delete(uri, null, null);
       }
       success = !file.exists();
    }
    catch (Exception e) {
       Log.e(TAG, “Error when deleting file “ + file.getAbsolutePath(), e);
       return false;
    }
 }

 if(success) scanFile(context, new String[]{ file.getPath() });
 return success;
}

This is how we achieved the functionality to delete album/albums from the storage in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/guide/topics/providers/document-provider#delete

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.MediaStore class – https://developer.android.com/reference/android/provider/MediaStore

4.Deleting files via Android media content provider – https://stackoverflow.com/questions/10925196/deleting-files-via-a-contentresolver-as-opposed-to-deleting-them-via-file-del

Continue Reading

Option to add description to an image in the Phimpme Android app

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to add some description to the image. So in this blog post, I will be discussing how we achieved the functionality to add a description to any image.

Step 1

First, we need to add an option in the overflow menu to add description for the image being viewed. The option to add description to an image has been achieved by adding the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/action_description”
  android:title=“Description”
  app:showAsAction=“never” />

Step 2

Now after the user chooses the option to add description to the image, an alertdialog with an edittext would be displayed to the user to enter the description. The dialog box with edittext has been implemented with the following lines of XML code.

<android.support.v7.widget.CardView
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/description_dialog_card”>
  <ScrollView
      android:layout_width=“match_parent”
      android:layout_height=“match_parent”>
      <LinearLayout
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:orientation=“vertical”>
          <TextView
              android:id=“@+id/description_dialog_title”
              android:layout_width=“match_parent”
              android:textColor=“@color/md_dark_primary_text”
              android:layout_height=“wrap_content”
              android:background=“@color/md_dark_appbar”
              android:padding=“24dp”
              android:text=“@string/type_description”
              android:textSize=“18sp”
              android:textStyle=“bold” />
          <LinearLayout
              android:id=“@+id/rl_description_dialog”
              android:layout_width=“match_parent”
              android:layout_height=“wrap_content”
              android:orientation=“horizontal”
              android:padding=“15dp”>
              <EditText
                  android:id=“@+id/description_edittxt”
                  android:layout_width=“fill_parent”
                  android:layout_height=“wrap_content”
                  android:padding=“15dp”
                  android:hint=“@string/description_hint”
                  android:textColorHint=“@color/grey”
                  android:layout_margin=“20dp”
                  android:gravity=“top|left”
                  android:inputType=“textCapSentences|textMultiLine”
                  android:maxLength=“2000”
                  android:maxLines=“4”
                  android:selectAllOnFocus=“true”/>
              <ImageButton
                  android:layout_width=“@dimen/mic_image”
                  android:layout_height=“@dimen/mic_image”
                  android:layout_alignRight=“@+id/description_edittxt”
                  app2:srcCompat=“@drawable/ic_mic_black”
                  android:layout_gravity=“center”
                  android:background=“@color/transparent”
                  android:paddingEnd=“10dp”
                  android:paddingTop=“12dp”
                  android:id=“@+id/voice_input”/>
          </LinearLayout>
      </LinearLayout>
  </ScrollView>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter description has been provided below.

Step 3

Now after retrieving the description added by the user, the description is saved in the realm database using the realm model object ImageDescModel which will contain the path of the image and the description added as its attributes. The path of the image has been used as the primary key for the description table. The realm model class used for the above-mentioned operation is described below.

public class ImageDescModel extends RealmObject {
  @PrimaryKey
  private String path;
  private String desc;

  public ImageDescModel() {
  }

  public ImageDescModel(String path, String title) {
      this.path = path;
      this.desc = title;
  }

  public String getId() {
      return path;
  }

  public void setId(String path) {
      this.path = path;
  }

  public String getTitle() {
      return desc;
  }

  public void setTitle(String description) {
      this.desc = description;
  }
}

This is how we have implemented the functionality to add description to images in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Realm for Android – https://realm.io/blog/realm-for-android/

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Working with realm tutorial – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/

Continue Reading

Option to rename albums in the Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, hiding an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to rename albums. So in this post, I will be discussing the implementation of the renaming functionality.

Step 1

First we need to add an option in the overflow menu to rename an album while we long-select an album or we are viewing photos inside a particular album. The option in the overflow menu can be added by integrating the following lines of code in the menu_albums.xml file(this file contains the xml code for overflow menu options provided for albums).

<item
android:id=”@+id/renameAlbum”
android:title=”@string/rename”
app:showAsAction=”never” />

Step 2

Now after the user selects the option in the overflow menu to rename the current viewing album, an alertdialog with an edittext would be displayed to the user to enter the new name for the album. The alertdialog containing the edittext has been implemented with the following lines of code.

<android.support.v7.widget.CardView
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/vn.mbm.phimp.me”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
app:cardCornerRadius=”2dp”
android:id=”@+id/dialog_chose_provider_title”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”>
<TextView
android:id=”@+id/rename_title”
android:layout_width=”match_parent”
android:textColor=”@color/md_dark_primary_text”
android:layout_height=”wrap_content”
android:background=”@color/md_dark_appbar”
android:padding=”24dp”
android:text=”@string/rename_photo_action”
android:textSize=”18sp”
android:textStyle=”bold” />
<RelativeLayout
android:id=”@+id/container_edit_text”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:padding=”24dp”/>
</LinearLayout>
</android.support.v7.widget.CardView>

The screenshot of the dialog to enter the new name is provided below.

Step 3

Finally, after retrieving the new name for the album entered by the user, the renameAlbum method is invoked by passing in the application context and new name as the parameters. Now inside the renameAlbum method, a new file path is created by the use of the getAlbumPathRenamed method of the StringUtils class and the current path of the album is changed to the new path generated earlier. Similarly path of each media inside the album is changed accordingly by the use of the method getPhotoPathRenamedAlbumChange of the StrinUtils class by looping through the media objects one by one. The lines of code implemented to perform the above-mentioned operations are provided below.

public boolean renameAlbum(final Context context, String newName) {
 found_id_album = false;
 boolean success;
 File newDir = new File(StringUtils.getAlbumPathRenamed(getPath(), newName));
 File oldDir = new File(getPath());
 success = oldDir.renameTo(newDir);
 if(success) {
    for (final Media m : media) {
       File from = new File(m.getPath());
       File to = new File(StringUtils.getPhotoPathRenamedAlbumChange(m.getPath(), newName));
       scanFile(context, new String[]{ from.getAbsolutePath() });
       scanFile(context, new String[]{ to.getAbsolutePath() }, new MediaScannerConnection.OnScanCompletedListener() {
          @Override
          public void onScanCompleted(String s, Uri uri) {
             if (!found_id_album) {
                id = MediaStoreProvider.getAlbumId(context, s);
                found_id_album = true;
             }
             Log.d(s, “onScanCompleted: “+s);
             m.setPath(s); m.setUri(uri.toString());
          }
       });
    }
    path = newDir.getAbsolutePath();
    name = newName;
 }
 return success;
}

This is how we have implemented the functionality to rename particular albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/media/MediaScannerConnection

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Media Scanner tutorial –https://stackoverflow.com/questions/13270789/how-to-run-media-scanner-in-android

Continue Reading

Selecting Multiple Tickets in Open Event Android

Open Event Android allows the user to select multiple tickets for an event this blog post will guide you on how its done. Ticket Id and quantity of ticket selected is stored in the form of List of pair. The first element of the List is the Id of the ticket and the second element is its quantity.

private var tickeIdAndQty = ArrayList<Pair<Int, Int>>()

Whenever the user selects any ticket using the dropdown the following snippet of code is executed. handleTicketSelect takes id and quantity of the selected ticket and search if any pair with passed id as the first element exists in the ticketIdAndQty list, if exists it updates that record else it inserts a new pair with given id and quantity into the list.

private fun handleTicketSelect(id: Int, quantity: Int) {
       val pos = ticketIdAndQty.map { it.first }.indexOf(id)
       if (pos == –1) {
           ticketIdAndQty.add(Pair(id, quantity))
       } else {
           ticketIdAndQty[pos] = Pair(id, quantity)
       }

When the user selects register the ticketIdAndQty list which contains details of selected tickets is added to the bundle along with the event id and are passed to attendee fragment. Attendee fragment allows the user to fill in other details such as country, payment option, first name etc.

Later attendee fragment with the help of attendee view model creates individual attendee on the server

rootView.register.setOnClickListener {
           if (!ticketsViewModel.totalTicketsEmpty(ticketIdAndQty)) {
               val fragment = AttendeeFragment()
               val bundle = Bundle()
               bundle.putLong(EVENT_ID, id)
               bundle.putSerializable(TICKET_ID_AND_QTY, ticketIdAndQty)
               fragment.arguments = bundle
               activity?.supportFragmentManager
                       ?.beginTransaction()
                       ?.replace(R.id.rootLayout, fragment)
                       ?.addToBackStack(null)
                       ?.commit()
           } else {
               handleNoTicketsSelected()
           }
       }

When the register is selected an attendee is created for every ticket id inside ticketIdandQty list wherever the second element of the pair that is quantity is greater than zero. FFirst name last name, email and other information is taken from the text views populated on the screen and attendee object is created using these data. Lastly, createAttendee view model function is called passing the generated attendee, country, and selected payment option. The former calls service layer function and created an attendee by making a post request to the server.

 ticketIdAndQty?.forEach {
                   if (it.second > 0) {
                       val attendee = Attendee(id = attendeeFragmentViewModel.getId(),
                               firstname = firstName.text.toString(),
                               lastname = lastName.text.toString(),
                               email = email.text.toString(),
                               ticket = TicketId(it.first.toLong()),
                               event = eventId)
                       val country = if (country.text.isEmpty()) country.text.toString() else null
                       attendeeFragmentViewModel.createAttendee(attendee, id, country, selectedPaymentOption)
                   }
               }

The above-discussed procedure creates one attendee for every ticket quantity which means if a user select ticket id 1 with 3 quantity and ticket id 2 with 5 as quantity total 8 attendees will be generated. Later after successfully generating the attendee’s order is created. Every order has an array of attendees along with other other related information.

Resources

Continue Reading

Updating User information in Open Event Android

A user can update its information such as first name, last name from the Edit Profile Fragment in Open Event Android. Edit Profile Fragment can be accessed from the menu inside the Profile page. On opening Edit Profile Fragment user can interact with the simple UI to update his/her information. This blog post will guide you on how its implemented in Open Event Android.

To update a User we send a patch request to Open Event Server. The patch request contains the Updated User as body and auth token as header and it returns the updated user in response. Following it what the interface method looks like

@PATCH(“users/{id}”)
fun updateUser(@Body user: User, @Path(“id”) id: Long): Single<User>

This method is exposed to the View Model using a service layer function which calls the above function and also inserts the returned user in the database.

fun updateUser(user: User, id: Long): Single<User> {
       return authApi.updateUser(user, id).map {
           userDao.insertUser(it)
           it
       }
   }

On using map on Single<User> returned by updateUser we can access the user inside the scope which is then inserted into the database using the DAO method insert user and the same user object is returned by the function.

This service layer method is then used in updateUser method of EditProfileViewModel class which specifies how it is subscribed and on which thread observer should be set etc. The Edit Profile Fragment fragment calls this method whenever the user clicks on the Update button.

fun updateUser(firstName: String, lastName: String) {
       val id = authHolder.getId()
       if (firstName.isEmpty() || lastName.isEmpty()) {
           message.value = “Please provide first name and last name!”
           return
       }
       compositeDisposable.add(authService.updateUser(User(id = id,          firstName = firstName, lastName = lastName), id)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .doOnSubscribe {
                   progress.value = true
               }
               .doFinally {
                   progress.value = false
               }
               .subscribe({
                   message.value = “User updated successfully!”
                   Timber.d(“User updated”)
               }) {
                   message.value = “Error updating user!”
                   Timber.e(it, “Error updating user!”)
               })
   }

UpdateUser takes two parameters first name and last name if any of these parameters is empty the function returns with an error message which is displayed on the UI else service layer update user function is called with argument a User object with first name and last name as provided to view model function and an id which is accessed using authHolder’s getId method. Whenever this is subscribed we set progress.value true which displays spinner on the UI this is set false after the operation is complete. If the patch request results in success then toast message is shown on screen and a success message is logged similar to this in case of error, an error toast is displayed and an error is logged.

This goes for the logic to update user we also need UI and menu item which launches this fragment.

Inside Menu.xml add the following snippet of code

<item
  android:id=“@+id/edit_profile”
  android:title=“@string/edit_profile” />

This will create a menu item inside the ProfileFragment. The next step is to wire this logic which tells what happens when the user selects this menu item. The following code wires it to EditProfileFragment.

R.id.edit_profile -> {
               val fragment = EditProfileFragment()              activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.frameContainer, fragment)?.addToBackStack(null)?.commit()
               return true

Resources

Continue Reading

Implement Sensor Data Fetching Using AsyncTask

In PSLab android app we have implemented sensor data fetching of various sensors inbuilt in the mobile device like light sensor, accelerometer, gyrometer. We can use PSLab to log the data and show in the form of the graph or maybe export the data in the form of CSV format for future use.

But recording data from the phone sensor imposes a serious problem in the performance of the Android app as it is a costly to process in terms of memory, resources and time. In CS terms there is too much work that has to be done on the single main thread which sometimes leads to lag and compromises the UX.

So as a solution we applied a concept of the Multithreading provided by Java in which we can shift the heavy process to a separate background thread so that the main thread never gets interrupted during fetching the sensor data and the background thread handles all the fetching and updates the UI as soon as it gets the data, till then the Main thread continues to serves the user so to user the application remains always responsive.

For implementing this we used a special class provided by Android Framework called AsyncTask. Which provides below methods:-

  • doInBackground() : This method contains the code which needs to be executed in the background. In this method, we can send results multiple times to the UI thread by publishProgress() method.

  • onPreExecute() : This method contains the code which is executed before the background processing starts.

  • onPostExecute() : This method is called after doInBackground method completes processing. Result from doInBackground is passed to this method.

  • onProgressUpdate() : This method receives progress updates from doInBackground() method, which is published via publishProgress() method, and this method can use this progress update to update the UI thread.

  • onCancelled(): This method is called when the background task has been canceled. Here we can free up resources or write some cleanup code to avoid memory leaks.

We created a class SensorDataFetch and extended this AsyncTask class and override its methods according to our needs.

private class SensorDataFetch extends AsyncTask<Void, Void, Void> implements SensorEventListener {

   private float data;
   private long timeElapsed;

   @Override
   protected Void doInBackground(Void... params) {
      
       sensorManager.registerListener(this, sensor, updatePeriod);
       return null;
   }

   protected void onPostExecute(Void aVoid) {
       super.onPostExecute(aVoid);
       visualizeData();
   }

   @Override
   protected void onPreExecute() {
 super.onPreExecute();
   //do nothing
   }

   @Override
   protected void onProgressUpdate(Void... values) {
       super.onProgressUpdate(values);
          //do nothing
   }

   @Override
   protected void onCancelled() {
       super.onCancelled();
          //do nothing
   }

In doInBackground() method we implemented the fetching raw data from the sensor by registering the listener and in onPostExecute() method we updated that data on the UI to be viewed by the user.

When this process is being run in the background thread the Main UI thread is free and remains responsive to the user. We can see in Figure 1 below that the UI is responsive to the user swipe action even when the sensor data is updating continuously on the screen.

Figure 1 shows Lux Meter responding to user swipe while fetching sensor data flawlessly.

 

Resources

https://developer.android.com/reference/android/os/AsyncTask – Android Developer documentation for Async Task class.

Continue Reading

Snackbar Implementation in PSLab Android App

In PSLab android app we have developed the functionality of logging sensor data in CSV format. We can start and stop the data recording using the save button in the upper right corner of the menu bar and toast message was shown to notify the user for logging status whether it is started or stopped but it leads to some problem like:-

  • The user doesn’t know where the logged file has been created in the external storage.
  • If the user accidentally clicked on the save button the data logging will start the user have to manually go the storage location and delete the recently created unwanted CSV file.

What’s the solution?

The solution to both these problem is solved by implementing Snackbar instead of Toast message.

According to Material Design documentation:-

The Snackbar widget provides brief feedback about an operation through a message at the bottom of the screen. Snackbar disappears automatically, either after a timeout or after a user interaction elsewhere on the screen, and can also be swiped off the screen.

Snackbar can also offer the ability to perform an action, such as undoing an action that was just taken or retrying an action that had failed.

 

Figure 1 shows a Snackbar sample
(Source: – https://material.io/develop/android/components/snackbar/ )

 

To implement the Snackbar in our Android app I started by creating a custom snack bar class which contains all the code to create and show the Snackbar on the screen.

public class CustomSnackBar {

   public static void showSnackBar(@NonNull CoordinatorLayout holderLayout,  
                                   @NonNull String displayText,
                                   String actionText, 
                                   View.OnClickListener clickListener){
       
   Snackbar snackbar =     
              Snackbar.make(holderLayout,displayText,Snackbar.LENGTH_LONG)
              .setAction(actionText, clickListener);

  //do your customization here
}

The custom class contains a static method ‘showSnackBar()’ having parameters:

Parameter Return Type Description
holderLayout CoordinatorLayout Container layout in which the snack bar will be shown at the bottom (should not be null)
displayText String Text to be displayed in the content of Snackbar (should not be null)
actionText String Clickable text which has some action associated with it
clickListener View.OnClickListener On click listener specifying an action to be performed when actionText is clicked

 

Inside the method, I called the static make()  method provided by the Snackbar class and passed holderlayout, displayText and duration of Snackbar in this case Snackbar.LENGTH_LONG as parameters.

Then I called setAction() and passed in the actionText and the clickListener as parameters in it to set the action text. If we pass in null no action text will be generated.

Then, if we want to changes the action text color we can do that by calling setActionTextColor() and passing in the desired color.

snackbar.setActionTextColor(ContextCompat.getColor(holderLayout.getContext(), R.color.colorPrimary));

And if we want to change the content text color then we need to first get the view then we need to get the instance of TextView containing the content text using findViewById() and passing android.support.design.R.id.snackbar_text which is default ID for context TextView, and then call setTextColor() to set the desired color.

View sbView = snackbar.getView();
   TextView textView =     
             sbView.findViewById(android.support.design.R.id.snackbar_text);
       textView.setTextColor(Color.WHITE);
   }

So, now our Snackbar engine is complete now we need to call CustomSnackBar class static method showSnackbar() in our sensor data logger.

For doing this I replaced all the instances of the Toast message with the ‘CustomSnackBar’ by passing in the desired messages that were being passed in Toast message.

But I still need to find the location of our stored CSV file and a method to delete the current generated CSV file.

For that, I did below modification to the CSVLogger class in PSLab android app.

public class CSVLogger {
   private static final String CSV_DIRECTORY = "PSLab";
   public CSVLogger(String category) {
       this.category = category;
       setupPath();
   }
   /*Below methods are included at the bottom of the class */
   public String getCurrentFilePath() {
       return Environment.getExternalStorageDirectory().getAbsolutePath() +
               File.separator + CSV_DIRECTORY + File.separator + category;
   }
   public void deleteFile() {
       csvFile.delete();
   }
}

Now for passing the location of the stored file and implementing delete option, I called the below method when the CSV logging is stopped by the user:

CustomSnackBar.showSnackBar((CoordinatorLayout) parent.findViewById(R.id.cl),

                    “CSV File stored at” + " " +lux_logger.getCurrentFilePath(),
  
                    “DELETE”,

                    new View.OnClickListener() {
                              Override
                              public void onClick(View view) {
                                             lux_logger.deleteFile();    
                              });

By doing this I get a Snackbar as shown in Figure 2, clicking on the “DELETE” text deletes the current CSV file.

Figure 2 shows snackbar showing file stored location and delete option

 

So, implementing Snackbar helped to make the app interactive and keeps user notified and control the data logging.

Resources

  1. https://www.journaldev.com/10324/android-snackbar-example-tutorial – Android SnackBar example implemetation tutorial
  2. https://material.io/develop/android/components/snackbar/ – Android Material Desing implementation of Snackbar.
Continue Reading
Close Menu
%d bloggers like this: