This blog is a step by step guide on how to make a functional progress bar in an Android app by taking an example of the progress bar implemented in PSLab Android application. The example is based on my work done in PSLab Android repository under PR #1077 and so it will only demonstrate making a simple progress bar (both linear and circular) and not the one showing progress in percentage too.
How progress bar was implemented in the PSLab app?
Both horizontal and circular progress bar is available in the Material Design Library provided by Google in Android Studio. So, no extra dependencies are needed.
Just drag and drop the progress bar of whichever shape necessary i.e. circular or horizontal, directly on the screen available in the Design tab as shown in Figure 1.
Figure 1. Design tab in Android Studio
There are two ways to use the progress bar available in the Material Design Library.
To show a loading screen
To show the progress of a process
Loading Screen
Loading Screens are used when the time that will be taken by the process is not known. So, an indeterminate circular progress bar is used to show that some process is going on and so the user needs to wait.
Layout
A circular progress bar is used for this process and so drag and drop the circular progress bar as shown in figure 1. Now set the position, height, and width of the progress bar in the layout as necessary. To set the color of the progress bar, use attribute android:indeterminateTint
Backend
To implement this type of functionality, use the setVisibility function to show the progress bar while some process is taking place in the backend and immediately remove it as soon as the result is ready to be displayed. To make the progress bar visible use progressBar.setVisibility(View.VISIBLE) and to make it invisible use progressBar.setVisibility(View.GONE)
Showing Progress
This is a very common type of process and is used by most of the apps. A horizontal progress bar is used to show the actual progress of the process taking place in the backend on a scale of 0-100 (the scale may vary) where 0 means the process hasn’t started and 100 means the result is now ready to be displayed.
Layout
Horizontal Progress bars are used for this type of usage. So, drag and drop the horizontal progress bar as shown in figure 1. Now set the position, height, and width of the progress bar in the layout as necessary. Different styles of the progress bar can be found in the documentation [1].
Backend
Initially, set the progress of the bar to 0 as no process is taking place by using method setProgress(). Now as soon as the process starts, to increase the progress by a fixed value, use progressBar.incrementProgressBy() method and to set the progress directly, use progressBar.setProgress() method.
So in this way, a progress bar can be implemented in an Android application. Other features like adding custom designs and animations can be done by making the necessary shapes and animations respectively and using the functions available in the documentation [1].
This blog covers the topic of how to create custom dialog boxes in an Android app by taking an example of how it was implemented in PSLab Android application. The custom dialog box that will be illustrated in this blog will have a layout similar to that from my PR feat: added a guide for Logic Analyzer Instrument using Alert Dialog Box in PSLab Android repository. But all the main functionalities that can be added to a custom dialog will be illustrated in this blog.
How custom dialog was implemented in the PSLab app?
The first step is to make a layout for the custom dialog box. The layout of custom dialog should be such that it shouldn’t occupy thewhole screen as the idea here is to overlay the dialog over the activity and not replace the whole activity with the dialog layout. The layout can be implemented by using the following attributes :
The result of the above layout after adding proper margins and padding is as shown in figure 1.
Figure 1. The output of the layout made for custom dialog
Now as the layout is ready, we can focus on adding Java code to inflate the layout over a particular activity. Make a function in the activity or the fragment in which the custom dialog is to be inflated. Add the following code (if the layout is same as that in PSLab app else modify the code as per the layout) in the function to display the custom dialog box.
publicvoidhowToConnectDialog(String title, String intro, int iconID, String desc){
try {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.custom_dialog_box, null);
// Find and set all the attributes used in the layout and then create the dialog as shown
builder.setView(dialogView);
builder.setTitle(title);
final AlertDialog dialog = builder.create();
ok_button.setOnClickListener(new View.OnClickListener() {
@OverridepublicvoidonClick(View v){
dialog.dismiss();
}
});
dialog.show();
} catch (Exception e) {
e.printStackTrace();
}
}
Here, the LayoutInflater inflates the custom dialog layout in the Alertdialog builder. The Alertdialog builder is used to hold and set values for all the views present in the inflated layout. The builder then creates a final custom dialog when builder.create() method is called. Finally, the dialog is shown by calling method dialog.show() and is dismissed by calling method dialog.dismiss().
Now the dialog box is ready and it will be inflated when the above method is called. But the “Don’t show again” checkbox currently has no functionality and so the dialog box will pop up although “Don’t show again” checkbox is ticked mark by the user. For adding this functionality, we will need to use Shared Preferences to save the state of the dialog box. For using Shared Preferences, first, add the following lines of code in the above method
final SharedPreferences settings = getActivity().getSharedPreferences(PREFS_NAME, 0);Boolean skipMessage = settings.getBoolean("skipMessage", false);
The PREFS_NAME is the key to distinctly identify the state of the given dialog box from the stored state of many dialog boxes (if any). A suggested key name is “customDialogPreference”. The skipMessage is a boolean used here to check the state if the dialog box should be inflated or not.
Now format the onClickListener of the OK button with the following code :
The following code checks the state of “Don’t show again” dialog and then inflates the custom dialog if applicable. If the checkbox is ticked mark then on pressing the OK button the dialog won’t be inflated again.
In this blog, we will use the data stored in the Realm to display a list of recorded experiments in the form of well defining card view items so that it is easier for the user to understand.
For showing the list we will make use of RecyclerView widget provided by Android which is a more advanced version of the List view and is used to display large data sets in a vertical list, horizontal list, grid, staggered grid etc.
RecyclerView works in accordance with RecyclerView Adapter which is core engine that is responsible of inflating the layout of list items, populating the items with data, recycling of list item views when they go out of viewing screen and much more.
For this blog, we are going to use a special RecyclerView Adapter provided by Realm itself because it integrates properly with the Realm Database and handles modifying, addition, deletion or updating of Realm data automatically and efficiently.
Step 1 Adding the dependencies
As always first we need to add the following code in our build.gradle file to add the dependency of Realm database and RealmRecyclerViewAdapter.
Step 2 Adding RecyclerView widget in our Activity layout file
First, we need to create an activity and name it as “DataLoggerActivity”, inside the layout of the Activity add the <RecyclerView> widget. This RecyclerView will act as a container of our list item.
Step 3 Creating the layout and View holder for the list item
We have to create the layout of the list item which will be inflated by the Adapter. So for this create an XML file in res folder and name it “data_list_item.xml”. For the list of the experiments, we want to show Name of the experiment, recording time, recording date for every list item. For this we will make use of <CardView> and <TextView>. This gist shows the code of xml file.
The layout of the list item created is shown in Figure 2
Now we need to create a view holder for this layout which we need to pass to the Adapter, the following code shows the implementation of View Holder for above list item layout.
In this step, we will start by creating a class called “SensorLoggedListAdpater” and for using use the RecyclerView adapter provided by Realm we need to make this class extend the RealmRecyclerViewAdpater class.
But for that we need to pass two generic parameter:
Model Class : This is class which define a Realm model, for this, we will pass a reference of “SensorLogged.class” which is defined in the previous blog as we want to show the list experiments which are stored using “SensorLogged” model class.
ViewHolder : For this, we will pass the ViewHolder that we have created in Step 3.
As every RecyclerView Adapter needs a arraylist which contains the list of object containing information which we have to populate on the list item, the RealmRecyclerViewAdpater needs data in form of RealmResult to operate on, so we will create a constructor and pass in the RealmResult list in the super() method which we need to provide when we initialize this adapter in our “DataLoggerActivity” class.
Now we need to override two methods provided by RealmRecyclerViewAdapter class that are:
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType): In which we will inflate the layout of list item “dta_list_tem.xml” which we have created in Step 3.
public void onBindViewHolder(@NonNull final ViewHolder holder, int position): In which we will populate the list item view using references stored in the ViewHolder with the data which we have provided while initializing the adapter.
@NonNull@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.logger_data_item, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
SensorLogged temp = getItem(position);
holder.sensor.setText(temp.getSensor());
Date date = new Date(temp.getDateTimeStart());
holder.dateTime.setText(String.valueOf(sdf.format(date)));
}
Step 5 Initializing the Adapter in Data Logger Activity and connecting with RecyclerView
Now we head to our Data Logger Activity, here in OnCreate() method first we will create a object of RecyclerView, then we will initialize our adapter by passing the RealmResult<SensorLogged> list which we have queried from the Realm Database.
Then we will set the LinearLayoutManager and finally, we will connect the the Adapter with the RecyclerView.
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.
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.
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.
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 classCustomSnackBar{
public staticvoid showSnackBar(@NonNull CoordinatorLayout holderLayout,
@NonNullString 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, displayTextand duration of Snackbar in this case Snackbar.LENGTH_LONG as parameters.
Then I called setAction() and passed in the actionText and theclickListeneras 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.
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.
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.
publicclass CSVLogger {
privatestatic final String CSV_DIRECTORY = "PSLab";
public CSVLogger(String category) {
this.category = category;
setupPath();
}
/*Below methods are included at the bottom of the class */publicString getCurrentFilePath() {
return Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + CSV_DIRECTORY + File.separator + category;
}
publicvoid 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:
Travis CI is a popular continuous integration tool built to test software and deployments done at GitHub repositories. They provide free plans to open source projects. PSLab Android is using Travis to ensure that all the pull requests made and the merges are build-bug frees. Travis can do this pretty well, but that is not all it can do. It’s a powerful tool and we can think of it as a virtual machine (with high specs) installed in a server for us to utilize efficiently.
There was a need in PSLab development that, if we want to test the functionalities of code at a branch. To fulfil this need, one has to download the branch to his local machine and use gradle to build a new apk. This is a tedious task, not to mention reviewers, even developers wouldn’t like to do this.
If the apk files were generated and in a branch, we can simply download them using a console command.
$ wget https://raw.github.com/fossasia/<repository>/<branch>/<file with extension>
With the help of Travis integration, we can create a simple script to generate these apks for us. This is done as follows;
Once the Travis build is complete for a triggering event such as a pull request, it will run it’s “after_success” block. We are going to execute a script at this point. Simply add the following code snippet.
Once the build is complete, there will be new folders in the virtual machine. One of them is the app folder. Inside this folder contains the build folder where all the apk files are generated. So the next step is to copy these apk files to a place of our preference. I am using a folder named apk to make it much sense.
Release apks are usually signed with a key and it would cause issues while installation. So we have to filter out the debug apk that we usually use for debugging and get it as the output apk. This involves simple file handling operations in Linux and a bit of git.
First of all, rename the apk file so that it will be different from other files.
# Rename apks with dev prefixes
mv app-debug.apk app-dev-debug.apk
Then add and commit them to a specific branch where we want the output from.
PSLab device is a compact electronic device with a variety of features. One of them is the ability to integrate sensors and get readings from them. One might think that why they should use PSLab device to get sensor readings and they can use some other hardware like Arduino. In those devices, user has to create firmware and need to know how to interface them with correct sampling rates and settings. But with PSLab, they all come in the whole package. User just have to plug the device to his phone and the sensor to device and he’s ready.
The idea of this blog post is to show how this sophisticated process is actually happening. Before that, let me give you a basic introduction on how I2C communication protocol works. I2C protocol is superior to UART and SPI protocols as they are device limited or requires many wires. But with I2C, you can literally connect thousands of sensors with just 4 wires. These wires are labeled as follows;
VCC – Power line
GND – Ground line
SDA – Data line
SCL – Signal clock
It is required that the SDA and SCL lines need to be connected to VCC line using two pull up resistors. But that’s just hardware. Let’s move on to learn how I2C communicate.
Here there is this communicating concept called master and slave. To start communication, master issues a global signal like a broadcast to all the devices connected to SCL and SDA lines. This signal contains the address of the slave, master needs to address and get data from. If the slave received this call to him, he will pull down the SDA line to signal the master that he heard him and ready to communicate with him. Here communication means reading or writing data. Then the communication happens and the link between master and slave breaks giving opportunity to other masters and slaves.
One might think this is a slow process. But these signals are transmitted at high frequencies. In PSLab it is at 100 kHz and that is one millisecond.
PSLab library has a special class to handle I2C communication. That is
publicclassI2C{/**/}
Once this class is initiated, one has to call the start function to start communication. This method requires the address we wish to communicate with and the mode of operation stating if it is a read or write operation
If there are no congestion in the lines such as reading from multiple devices, the acknowledgement will be instantaneous. Once that is complete, we can start communication either byte-wise or bulk-wise
PSLab Android app supports multiple sensors external and internal. Users can view sensor readings and record them into a csv file for later use. They can also have their location embedded into the data set they are recording. Saving the location related to each sensor reading is important. Say in a school experiment teacher can ask the student to measure dust particle concentration in city and inside his house. If the data set didn’t have any reference to the location where it was recorded, it is just incomplete. To facilitate this feature, we have enabled location in data recordings.
Enabling location is just not enough. User should be able to view the locations. Probably on a map itself would be a good idea. With the use of Open Street Maps, we can add markers to specific locations. These markers can be used as a point to show on map where a specific data set had been recorded by the user. This can be implemented further to add additional features such as standardized labels to view which data set is at which location etc.
Figure 1: Markers on Map
Unlike Google Maps API, in Open Street Maps there is no direct implementation to add a marker. But it is not a hard implementation either. We can simply create a class extending map overlays and use that as a base to add markers.
We can start by creating a new class that extends ItemizedOverlay<OverlayItem> class as follows. In this class, it would be wise to have an array list full of markers we are using in the map to modularize the whole markers related tasks into this one class rather than implementing in every place where map is used.
Once the class is initiated, have the following methods implemented. The following method will add markers to the array list we have created at the beginning.
Once the overlay class is completed, we can move on to actual implementation of a map on Openstreetmap view.
From the main activity where the map is viewed, initiate the marker overlay class and pass the drawable that needs to be shown as the marker to the class constructor as follows:
PSLab Android app is an open source app that uses fully open libraries and tools so that the community can use all it’s features without any compromises related to pricing or feature constraints. This will brings us to the topic how to implement a map feature in PSLab Android app without using proprietary tools and libraries. This is really important as now the app is available on Fdroid and they don’t allow apps to have proprietary tools in them if they are published there. In other words, it simply says we cannot use Google Maps APIs no matter how powerful they are in usage.
There is a workaround and that is using Open Street Maps (OSM). OSM is an open source project which is supported by a number of developers all around the globe to develop an open source alternative to Google Maps. It supports plenty of features we need in PSLab Android app as well. Starting from displaying a high resolution map along with caching the places user has viewed, we can add markers to show data points and locations in sensor data logging implementations.
All these features can be made available once we add the following dependencies in gradle build file. Make sure to use the latest version as there will be improvements and bug fixes in each newer version
In a view xml file, add the layout org.osmdroid.views.MapView to initiate the map view. There is a known issue in OSM library. That is during the initiation, if the zoom factor is set to a small value, there will be multiple instances of maps as shown in Fig 1. The solution is to have a higher zoom value when the map is loaded.
Figure 1: Multiple map tiles in OSM
Once we initialize the map view inside an activity, a zoom level can be easily set using a map controller as follows;
After successfully implementing the map view, we can develop the business logic to add markers and descriptions to improve the usability of OS Maps. They will be available in the upcoming blog posts.
PSLab Android app by FOSSASIA can be used to visualize different waveforms, signal levels and patterns. Many of them involve logging data from different instruments. These data sets can be unique and the user might want them to be specific to a location or a time. To facilitate this feature, PSLab Android app offers a feature to save user’s current location along with the data points.
This implementation can be done in two ways. One is to use Google Maps APIs and the other one is to use LocationManager classes provided by Android itself. The first one is more on to proprietary libraries and it will give errors when used in an open source publishing platform like Fdroid as they require all the libraries used in an app to be open. So we have to go with the latter, using LocationManager classes.
As first step, we will have to request permission from the user to allow the app access his current location. This can be easily done by adding a permission tag in the Manifest.xml file.
Since we are not using Google Maps APIs, capturing the current location will take a little while and that can be considered as the only downfall of using this method. We have to constantly check for a location change to capture the data related to current location. This can be easily done by attaching a LocationListener as it will do the very thing for us.
In case if the user has turned off GPS in his device, this method wouldn’t work. We will have to request him to turn the feature on using a simple dialog box or a bottom sheet dialog.
We can also customize how frequent the locationlistener should check for a location using another class named LocationManager. This class can be instantiated as follows:
Then we can easily set the time interval using requestLocationUpdates method. Here I have requested location updates in every one second. That is a quite reasonable rate.
Once we have set all this up, we can capture the current location assuming that the user has turned on the GPS option from his device settings and the LocationManager class has a new location as we checked earlier.
Each location will contain details related to its position such as latitudes and longitudes. We can log these data using the CSVLogger class implementation in PSLab Android app and let user have this option while doing his experiments.