There is no way a user can turn off a specific type of Notifications unless a way is explicitly provided in the app. The only way around is to turn off all the app Notifications. With Android O, Developers can create different channels for different types of notifications. Users can modify following characteristics from settings:
Importance
Notification Sound (Tones)
Blink Light
Vibration
Lock Screen
Do Not Disturb
Creating Notification Channel
A Notification Channel can be created by instantiating a NotificationChannel reference. A Notification channel takes channel id, channel name, and importance.
The channel id is different and has to be unique for each channel.
Here ‘sports’ is the channel id and ‘Sports’ is the channel name.
Creating Notification
We can add notification to a channel by making a slight change in our old way of creating a notification.
Notification.Builder builder1 = new Notification.Builder(getApplicationContext(), "sports")
.setContentTitle("Sports")
.setContentText("India V/s New Zealand match postponed due to rain")
.setSmallIcon(R.drawable.ic_launcher_background)
.setChannelId("sports") //Specifying channel id of notification
.setAutoCancel(true);
Just by setting the channel id in the notification, It will be shown in its corresponding channel.
You can download the sample code for this blog from here.
Android provides a set of libraries to help to design of a highly maintainable and robust code. They have provided with helper classes for better data caching and handling UI component states.
Room
The Room is a robust SQL object mapping library. It provides a layer of Abstraction over the Android’s SQLite Database. Allowing us to fluently access the power of SQLite database.
There are 3 major components in Room:
Database: Contains the database holder and serves as the main access point for the app’s persisted relational data.
Entity: Represents a table within the database.
DAO:
Contains the methods used for accessing the database.
Boiler Plate Code
In this tutorial we will be making a very basic Todo Application, that will perform CRUD operations using the Room Database.
The boilerplate code can create, update and delete a todo/memo. However, the data won’t persist as soon as the app is closed.
Our main screen is a having a recyclerview to list all of our todos. The model of the todo is saved in the model package which is as below:
public class Todo {
private long _id;
private String todoTitle;
private String todoText;
private long timeStamp;
public long get_id() {
return _id;
}
public void set_id(long _id) {
this._id = _id;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public String getTodoText() {
return todoText;
}
public void setTodoText(String todoText) {
this.todoText = todoText;
}
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
}
We will be using this class as an Entity (Table) for our database. Through annotations, Room makes it much easier to define the properties of the table. Annotations like PrimaryKey, ColumnInfo are quite understandable by name.
A table is created using @Entity annotation and the ‘tableName’ attributes are used to provide a table name. We define primary key of the table using @PrimaryKey annotation and columns of the table using @ColumnInfo annotation and their respective names with a name attribute.
Our model class after adding annotations will look as below:
@Entity(tableName = "todo")
public class Todo {
@PrimaryKey
private long _id;
@ColumnInfo(name = "title")
private String todoTitle;
@ColumnInfo(name = "text")
private String todoText;
@ColumnInfo(name = "time")
private long timeStamp;
public long get_id() {
return _id;
}
public void set_id(long _id) {
this._id = _id;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public String getTodoText() {
return todoText;
}
public void setTodoText(String todoText) {
this.todoText = todoText;
}
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
}
Next, We will be making our interface for performing CRUD operations.
The dao interface has to be annotated with @Dao annotation. We use @Query annotation for executing a query and @Insert, @Update and @Delete for insertion, deletion, and updating respectively.
The dao interface will finally look as below:
@Dao
public interface AppDao {
@Query("Select * from " + AppConstants.DB_NAME + " order by time desc")
List<Todo> getAllTodos();
@Query("Select * from " + AppConstants.DB_NAME + " where _id = :id order by time desc")
Todo getTodo(String id);
@Insert
void insertTodo(Todo todo);
@Update
void updateTodo(Todo todo);
@Delete
void deleteTodo(Todo todo);
}
Attribute values can also be passed via interface method arguments and are used in queries with ‘:’ sign. As we can see in case of ‘getTodo’.
And at last, we will create an abstract Database Class that will extend RoomDatabase class.
Database class is created through @Database annotation, it is mandatory to specify entities and version of the database. In our Database class, we will add access methods of our each Dao class.
The AppDatabase class is as below:
@Database(entities = {Todo.class}, version = 1) extends RoomDatabase {
public abstract AppDao appDao();
}
We will also be using a Singleton class to get the reference to our AppDao interface. Which is as below:
public class AppDBHandler {
private AppDao appdao;
private static AppDBHandler ourInstance;
public static AppDBHandler getInstance(Context context) {
if(ourInstance == null) {
ourInstance = new AppDBHandler(context);
}
return ourInstance;
}
private AppDBHandler(Context context) {
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, AppConstants.DB_NAME).build();
appdao = db.appDao();
}
public AppDao getAppDao() {
return appdao;
}
}
At last, we are all done with setting up database boilerplate for the project. Now the only thing left to do is adding database operations in our project using our Dao interface.
The Room directly maps the database model to our Java Bean.
In our MainActivity.class, we will add following lines to retrieve the list of all todos. Just below setting adapter to our recyclerview.
MainActivity.java
...
...
recyclerView.setAdapter(todoListAdapter);
final AppDao dao = AppDBHandler.getInstance(getApplicationContext()).getAppDao();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
todos.addAll(dao.getAllTodos());
runOnUiThread(new Runnable() {
@Override
public void run() {
todoListAdapter.notifyItemRangeInserted(0, todos.size());
}
});
}
});
T1.start();
Note: All the database calls in Room are synchronous and run on same thread to which they are called. If the call is a made on UI thread, then it will throw a IllegalStateException.
MainActivity.java
...
...
case 1: {
final AppDao dao = AppDBHandler.getInstance(getApplicationContext()).getAppDao();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
dao.deleteTodo(todos.get(position));
todos.remove(position);
runOnUiThread(new Runnable() {
@Override
public void run() {
Snackbar.make(parentLayout, "Todo Deleted", Snackbar.LENGTH_SHORT).show();
todoListAdapter.notifyItemRemoved(position);
}
});
}
});
T1.start();
break;
}
We are using a single Dialog for creating and editing new todos. In our logic, we are using a private Todo class reference null value of which signifies whether the action is an update or a create action. We will modify our existing logic as below:
CreateTodoDialog.java
...
...
@Override
public void onClick(View view) {
...
...
if(this.todo == null) {
todo = new Todo();
todo.set_id(System.currentTimeMillis());
todo.setTimeStamp(System.currentTimeMillis());
todo.setTodoTitle(edtTitle.getText().toString());
todo.setTodoText(edtText.getText().toString());
final AppDao dao = AppDBHandler.getInstance(getContext()).getAppDao();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
dao.insertTodo(todo);
dataCallbackListener.onDataReceived(todo);
}
});
T1.start();
} else {
todo.setTimeStamp(System.currentTimeMillis());
todo.setTodoTitle(edtTitle.getText().toString());
todo.setTodoText(edtText.getText().toString());
final AppDao dao = AppDBHandler.getInstance(getContext()).getAppDao();
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
dao.updateTodo(todo);
dataCallbackListener.onDataReceived(todo);
}
});
T1.start();
}
And that’s it, the Room itself will use the provided pojo for performing insert and update operations.
Remarks
I believe that it will be very helpful in creating applications that require data caching and have well structure models for reusability. Especially, e-commerce applications in which we can model our product details like and can easily use them in carts and product info sections (Eg. quantity, favorites) where we can prevent unnecessary API calls.
One of the most exciting announcements by Google in I/O 17 was providing support for custom fonts. Not as an asset but fonts can now be used as a resource. An application can also use one of many Google Fonts dynamically. As fonts are now supported natively, the same font can be used by different applications.
The feature is extended to support API version 14 and above through Support Library 26. Below is a brief overview of how fonts are used by applications:
FontContract is a Utility class that deals with Font Content Provider. Downloaded Fonts are cache within the device. Since multiple devices share a single endpoint for fonts. Hence, fonts are shared across different applications.
Advantages of using Downloadable fonts:
Reduced APK size. As fonts are no longer needed to be kept in APK.
Fonts are shared by different applications through a single provider. Hence it saves phone memory and user data. Fonts are downloaded only when they are needed.
Where Downloadable fonts might not work:
Fonts may fail to load/download depending upon network connection, in which case default font are used.
Downloadable fonts require a Font Provider, for eg. Google Fonts are provided by Google Play Services.
With Support Library and Android O, Fonts can be used with these two features:
Creating a new project in Android Studio 3.0, we can see a ‘font’ directory in our ‘res’ folder. We can add our custom fonts(ttf and otf) in this directory and can use them in the project like we use other resources. More than that, you can also create font family where you can define different fonts with styles and weights.
The Ubuntu font can be directly used in a View like AppCompatButton, AppCompatTextView using ‘fontFamily’ tag as below:
The fonts are no longer supposed to be a bundled with the apk, but instead, they are downloaded on demand from Font Provider when are needed by an Application. If other apps in the system use the same font the fonts are reused. In case fonts fail to download, then default system fonts are used.
Let’s see how simple it is to implement a font provider using XML.
In layout editor attributes, we can find a fontFamily property of the view (AppCompatTextView in our case). Clicking on the drop-down, we will see More Fonts.. option in the bottom.
We will this dialog after selecting More Fonts option from where we can pick any of the google fonts from there and the studio will set up everything for us!
Fonts can be downloaded dynamically using Java/Kotlin code. We start by creating a FontRequest. A FontRequest requires a provider authority, provider package, font query and certificate array. Parameters are same as of the XML parameters.
class Activity:AppCompatActivity() {
....
private var regular: AppCompatTextView? = null
private val certificate = R.array.com_google_android_gms_fonts_certs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_labels_no_fonts)
regular = findViewById(R.id.tv_regular)
...
...
val sansRegularRequest = FontRequest(Companion.PROVIDER_AUTHORITY, PROVIDER_PACKAGE, "Open Sans", certificate)
val sansRegularCallback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface?) {
// Called When fonts are loaded/downloaded
regular!!.typeface = typeface
}
override fun onTypefaceRequestFailed(reason: Int) {
// Called When fonts fail to loaded/downloaded, Here reason is the error code
// Read more about reasons from here:
// https://developer.android.com/reference/android/provider/FontsContract.FontRequestCallback.html#FAIL_REASON_FONT_LOAD_ERROR
}
}
FontsContractCompat.requestFont(this, sansRegularRequest, sansRegularCallback, Handler())
}
companion object {
private val PROVIDER_PACKAGE = "com.google.android.gms"
private val PROVIDER_AUTHORITY = PROVIDER_PACKAGE + ".fonts"
}
}
As a callback to FontRequest, we use FontsContract.FontRequestCallback object, that provides us with two callback methods to handle FontRequest responses.
onTypefaceRetrieved method is called when the font is downloaded. It provides with a TypeFace object of the requested font.
onTypefaceRequestFailed method is called when the queried font fails to load, the method argument provides with an int error code for reason of failure. You can read about various error codes here.
FontsContractCompat.requestFont method is used to make request of font.
Note: The last argument of FontsContractCompat.requestFont takes a handler thread as an input, just make sure that it isn’t a UI thread.
And here is the final app after implementing all weighted fonts.
Remarks
I personally think this will actually make it easier to develop highly customized apps in a better way. Gone are the days when all fonts were a part of app assets.
We cannot ignore the fact that fonts are executable, hence they come with their own security risks.