Here is the preview of the app that we are going to develop during the series.

1. Technology stack
A flexible platform for creating offline-first, reactive mobile apps effortlessly.
This platform integrates two technologies we are going to use:
Realm Object Server
Realm Object Server (released around September 2016) is basically a backend responsible for realtime data synchronization, resolving conflicts in data changes, handling various events and authentication. In can be deployed on servers or in the cloud.
Realm Mobile Database
Realm Mobile Database is a cross-platform database, supporting both iOS and Android and it’s open source. For those who are familiar with SQLite database (default choice of persistence in Android) or ORM libraries like ORMLite/ActiveAndroid/GreenDao, you should know that in Realm gives no SQLite at all. Realm Mobile Database is a Zero-copy object store. Sounds familiar? Probably not (I had not known that before attending the Droidcon NYC 2015 – Realm: Building a mobile database).
In this article we are going to use the Developer Edition of RPM.
The instance of our Realm Object Server backend will be available through the Ubuntu instance.
2. Useful links
3. TaskManager UI
Before getting to the “cool” stuff, we are going to prepare some basic UI for the app. However, if you are impatient (like I am), you can just checkout to commit 09adf4d in the TaskManager github repository and start from there.
3.1 Gradle
Core dependencies for UI.
compile 'com.android.support:appcompat-v7:25.0.1'
compile "com.android.support:support-annotations:25.0.1"
compile "com.android.support:support-v13:25.0.1"
compile "com.android.support:design:25.0.1"
compile "com.android.support:recyclerview-v7:25.0.1"
compile "com.android.support:cardview-v7:25.0.1"
compile "com.jakewharton:butterknife:7.0.1"
compile 'com.gordonwong:material-sheet-fab:1.2.1' //Library containing the fab button
compile 'com.android.support:appcompat-v7:25.0.1'
compile "com.android.support:support-annotations:25.0.1"
compile "com.android.support:support-v13:25.0.1"
compile "com.android.support:design:25.0.1"
compile "com.android.support:recyclerview-v7:25.0.1"
compile "com.android.support:cardview-v7:25.0.1"
compile "com.jakewharton:butterknife:7.0.1"
compile 'com.gordonwong:material-sheet-fab:1.2.1' //Library containing the fab button
compile 'com.android.support:appcompat-v7:25.0.1'
compile "com.android.support:support-annotations:25.0.1"
compile "com.android.support:support-v13:25.0.1"
compile "com.android.support:design:25.0.1"
compile "com.android.support:recyclerview-v7:25.0.1"
compile "com.android.support:cardview-v7:25.0.1"
compile "com.jakewharton:butterknife:7.0.1"
compile 'com.gordonwong:material-sheet-fab:1.2.1' //Library containing the fab button
3.2 Main screen UI

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/toolbar" />
<include layout="@layout/fab" />
</android.support.design.widget.CoordinatorLayout>
<include layout="@layout/sheet" />
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<include layout="@layout/toolbar" />
<include layout="@layout/fab" />
</android.support.design.widget.CoordinatorLayout>
<include layout="@layout/sheet" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<include layout="@layout/toolbar" />
<include layout="@layout/fab" />
</android.support.design.widget.CoordinatorLayout>
<include layout="@layout/sheet" />
</RelativeLayout>
toolbar.xml
Basic toolbar with a title.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:text="TaskManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:text="TaskManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textStyle="bold"
android:textSize="16sp"
android:singleLine="true" />
</FrameLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:text="TaskManager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textStyle="bold"
android:textSize="16sp"
android:singleLine="true" />
</FrameLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</merge>
fab.xml
Custom implementation of fab button (copied from this sample)
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<pl.adriankremski.realmtaskmanager.views.Fab
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_create_white_24dp"
app:layout_anchor="@id/recycler"
app:layout_anchorGravity="bottom|right|end"/>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<pl.adriankremski.realmtaskmanager.views.Fab
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:clickable="true"
android:src="@drawable/ic_create_white_24dp"
app:layout_anchor="@id/recycler"
app:layout_anchorGravity="bottom|right|end"/>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<pl.adriankremski.realmtaskmanager.views.Fab
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:clickable="true"
android:src="@drawable/ic_create_white_24dp"
app:layout_anchor="@id/recycler"
app:layout_anchorGravity="bottom|right|end"/>
</merge>
Fab.java
public class Fab extends FloatingActionButton implements AnimatedFab {
private static final int FAB_ANIM_DURATION = 200;
public Fab(Context context) {
public Fab(Context context, AttributeSet attrs) {
public Fab(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
* Shows the FAB and sets the FAB's translation.
* @param translationX translation X value
* @param translationY translation Y value
public void show(float translationX, float translationY) {
setTranslation(translationX, translationY);
// Only use scale animation if FAB is hidden
if (getVisibility() != View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + translationX;
float pivotY = getPivotY() + translationY;
// If pivots are 0, that means the FAB hasn't been drawn yet so just use the
if (pivotX == 0 || pivotY == 0) {
anim = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
anim = new ScaleAnimation(0, 1, 0, 1, pivotX, pivotY);
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
setVisibility(View.VISIBLE);
// Only use scale animation if FAB is visible
if (getVisibility() == View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + getTranslationX();
float pivotY = getPivotY() + getTranslationY();
ScaleAnimation anim = new ScaleAnimation(1, 0, 1, 0, pivotX, pivotY);
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
setVisibility(View.INVISIBLE);
private void setTranslation(float translationX, float translationY) {
animate().setInterpolator(getInterpolator()).setDuration(FAB_ANIM_DURATION)
.translationX(translationX).translationY(translationY);
private Interpolator getInterpolator() {
return AnimationUtils.loadInterpolator(getContext(), R.interpolator.msf_interpolator);
public class Fab extends FloatingActionButton implements AnimatedFab {
private static final int FAB_ANIM_DURATION = 200;
public Fab(Context context) {
super(context);
}
public Fab(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Fab(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Shows the FAB.
*/
@Override
public void show() {
show(0, 0);
}
/**
* Shows the FAB and sets the FAB's translation.
*
* @param translationX translation X value
* @param translationY translation Y value
*/
@Override
public void show(float translationX, float translationY) {
// Set FAB's translation
setTranslation(translationX, translationY);
// Only use scale animation if FAB is hidden
if (getVisibility() != View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + translationX;
float pivotY = getPivotY() + translationY;
ScaleAnimation anim;
// If pivots are 0, that means the FAB hasn't been drawn yet so just use the
// center of the FAB
if (pivotX == 0 || pivotY == 0) {
anim = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
} else {
anim = new ScaleAnimation(0, 1, 0, 1, pivotX, pivotY);
}
// Animate FAB expanding
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
startAnimation(anim);
}
setVisibility(View.VISIBLE);
}
/**
* Hides the FAB.
*/
@Override
public void hide() {
// Only use scale animation if FAB is visible
if (getVisibility() == View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + getTranslationX();
float pivotY = getPivotY() + getTranslationY();
// Animate FAB shrinking
ScaleAnimation anim = new ScaleAnimation(1, 0, 1, 0, pivotX, pivotY);
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
startAnimation(anim);
}
setVisibility(View.INVISIBLE);
}
private void setTranslation(float translationX, float translationY) {
animate().setInterpolator(getInterpolator()).setDuration(FAB_ANIM_DURATION)
.translationX(translationX).translationY(translationY);
}
private Interpolator getInterpolator() {
return AnimationUtils.loadInterpolator(getContext(), R.interpolator.msf_interpolator);
}
}
public class Fab extends FloatingActionButton implements AnimatedFab {
private static final int FAB_ANIM_DURATION = 200;
public Fab(Context context) {
super(context);
}
public Fab(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Fab(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Shows the FAB.
*/
@Override
public void show() {
show(0, 0);
}
/**
* Shows the FAB and sets the FAB's translation.
*
* @param translationX translation X value
* @param translationY translation Y value
*/
@Override
public void show(float translationX, float translationY) {
// Set FAB's translation
setTranslation(translationX, translationY);
// Only use scale animation if FAB is hidden
if (getVisibility() != View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + translationX;
float pivotY = getPivotY() + translationY;
ScaleAnimation anim;
// If pivots are 0, that means the FAB hasn't been drawn yet so just use the
// center of the FAB
if (pivotX == 0 || pivotY == 0) {
anim = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
} else {
anim = new ScaleAnimation(0, 1, 0, 1, pivotX, pivotY);
}
// Animate FAB expanding
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
startAnimation(anim);
}
setVisibility(View.VISIBLE);
}
/**
* Hides the FAB.
*/
@Override
public void hide() {
// Only use scale animation if FAB is visible
if (getVisibility() == View.VISIBLE) {
// Pivots indicate where the animation begins from
float pivotX = getPivotX() + getTranslationX();
float pivotY = getPivotY() + getTranslationY();
// Animate FAB shrinking
ScaleAnimation anim = new ScaleAnimation(1, 0, 1, 0, pivotX, pivotY);
anim.setDuration(FAB_ANIM_DURATION);
anim.setInterpolator(getInterpolator());
startAnimation(anim);
}
setVisibility(View.INVISIBLE);
}
private void setTranslation(float translationX, float translationY) {
animate().setInterpolator(getInterpolator()).setDuration(FAB_ANIM_DURATION)
.translationX(translationX).translationY(translationY);
}
private Interpolator getInterpolator() {
return AnimationUtils.loadInterpolator(getContext(), R.interpolator.msf_interpolator);
}
}
sheet.xml
Sheet for adding new tasks in our application.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Overlay that dims the screen -->
<com.gordonwong.materialsheetfab.DimOverlayFrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Circular reveal container for the sheet -->
<io.codetail.widget.RevealLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end|bottom"
android:orientation="vertical">
<!-- Sheet that contains your items -->
<android.support.v7.widget.CardView
android:id="@+id/fab_sheet"
android:layout_width="350dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal">
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="top"
android:gravity="top|left"
android:inputType="textMultiLine"
android:singleLine="false" />
android:id="@+id/add_task"
android:background="@drawable/ripple"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:textAllCaps="true" />
</android.support.v7.widget.CardView>
</io.codetail.widget.RevealLinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Overlay that dims the screen -->
<com.gordonwong.materialsheetfab.DimOverlayFrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Circular reveal container for the sheet -->
<io.codetail.widget.RevealLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end|bottom"
android:orientation="vertical">
<!-- Sheet that contains your items -->
<android.support.v7.widget.CardView
android:id="@+id/fab_sheet"
android:layout_width="350dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal">
<EditText
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="top"
android:gravity="top|left"
android:inputType="textMultiLine"
android:lines="4"
android:maxLines="6"
android:minLines="4"
android:singleLine="false" />
<Button
android:id="@+id/add_task"
android:background="@drawable/ripple"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:textColor="#fff"
android:textStyle="bold"
android:text="Add task"
android:textAllCaps="true" />
</android.support.v7.widget.CardView>
</io.codetail.widget.RevealLinearLayout>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Overlay that dims the screen -->
<com.gordonwong.materialsheetfab.DimOverlayFrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Circular reveal container for the sheet -->
<io.codetail.widget.RevealLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="end|bottom"
android:orientation="vertical">
<!-- Sheet that contains your items -->
<android.support.v7.widget.CardView
android:id="@+id/fab_sheet"
android:layout_width="350dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal">
<EditText
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="top"
android:gravity="top|left"
android:inputType="textMultiLine"
android:lines="4"
android:maxLines="6"
android:minLines="4"
android:singleLine="false" />
<Button
android:id="@+id/add_task"
android:background="@drawable/ripple"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:textColor="#fff"
android:textStyle="bold"
android:text="Add task"
android:textAllCaps="true" />
</android.support.v7.widget.CardView>
</io.codetail.widget.RevealLinearLayout>
</merge>
3.3 MainActivity
Finally, let’s connect everything in our MainActivity.
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
private MaterialSheetFab materialSheetFab;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = ButterKnife.findById(this, R.id.toolbar);
fab.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.colorPrimary));
setSupportActionBar(toolbar);
private void setupMaterialSheetFab() {
int sheetColor = ContextCompat.getColor(this, R.color.background_light);
int fabColor = ContextCompat.getColor(this, R.color.colorPrimary);
materialSheetFab = new MaterialSheetFab(fab, sheetView, overlayView,
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) sheetView.getLayoutParams();
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
params.width = (int) (size.x * 0.9); // we don't want our sheet to cover whole width of the screen
sheetView.setLayoutParams(params);
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
public void onBackPressed() {
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
public class MainActivity extends AppCompatActivity {
@Bind(R.id.recycler)
RecyclerView recyclerView;
@Bind(R.id.fab)
Fab fab;
@Bind(R.id.fab_sheet)
View sheetView;
@Bind(R.id.overlay)
View overlayView;
@Bind(R.id.text)
TextView textInputField;
private MaterialSheetFab materialSheetFab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = ButterKnife.findById(this, R.id.toolbar);
fab.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.colorPrimary));
setSupportActionBar(toolbar);
setupMaterialSheetFab();
}
private void setupMaterialSheetFab() {
int sheetColor = ContextCompat.getColor(this, R.color.background_light);
int fabColor = ContextCompat.getColor(this, R.color.colorPrimary);
materialSheetFab = new MaterialSheetFab(fab, sheetView, overlayView,
sheetColor, fabColor);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) sheetView.getLayoutParams();
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
params.width = (int) (size.x * 0.9); // we don't want our sheet to cover whole width of the screen
sheetView.setLayoutParams(params);
}
@OnClick(R.id.add_task)
public void addTask() {
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
}
}
@Override
public void onBackPressed() {
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
} else {
super.onBackPressed();
}
}
}
public class MainActivity extends AppCompatActivity {
@Bind(R.id.recycler)
RecyclerView recyclerView;
@Bind(R.id.fab)
Fab fab;
@Bind(R.id.fab_sheet)
View sheetView;
@Bind(R.id.overlay)
View overlayView;
@Bind(R.id.text)
TextView textInputField;
private MaterialSheetFab materialSheetFab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = ButterKnife.findById(this, R.id.toolbar);
fab.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.colorPrimary));
setSupportActionBar(toolbar);
setupMaterialSheetFab();
}
private void setupMaterialSheetFab() {
int sheetColor = ContextCompat.getColor(this, R.color.background_light);
int fabColor = ContextCompat.getColor(this, R.color.colorPrimary);
materialSheetFab = new MaterialSheetFab(fab, sheetView, overlayView,
sheetColor, fabColor);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) sheetView.getLayoutParams();
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
params.width = (int) (size.x * 0.9); // we don't want our sheet to cover whole width of the screen
sheetView.setLayoutParams(params);
}
@OnClick(R.id.add_task)
public void addTask() {
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
}
}
@Override
public void onBackPressed() {
if (materialSheetFab.isSheetVisible()) {
materialSheetFab.hideSheet();
} else {
super.onBackPressed();
}
}
}
Result

3.4 Managing tasks
Since our basic UI of the main screen is done, we can now proceed to implement one of our main functionalities. To do that, we are going to define the Task model, corresponding TaskRowHolder, and finally the TaskAdapter. These classes are very simple, so I guess that no explanation is needed.
Task
private boolean completed;
public Task(String text) {
public boolean isCompleted() {
public void setCompleted(boolean completed) {
this.completed = completed;
public String getText() {
public void setText(String text) {
public class Task {
private boolean completed;
private String text;
public Task(String text) {
this.text = text;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
public class Task {
private boolean completed;
private String text;
public Task(String text) {
this.text = text;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
TaskRowHolder
public class TaskRowHolder extends RecyclerView.ViewHolder {
public TaskRowHolder(View itemView) {
ButterKnife.bind(this, itemView);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
task.setCompleted(isChecked);
public void setTask(Task task) {
taskNameLabel.setText(task.getText());
checkBox.setChecked(task.isCompleted());
public class TaskRowHolder extends RecyclerView.ViewHolder {
@Bind(R.id.task_name)
TextView taskNameLabel;
@Bind(R.id.checkbox)
CheckBox checkBox;
private Task task;
public TaskRowHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
task.setCompleted(isChecked);
}
});
}
public void setTask(Task task) {
this.task = task;
taskNameLabel.setText(task.getText());
checkBox.setChecked(task.isCompleted());
}
}
public class TaskRowHolder extends RecyclerView.ViewHolder {
@Bind(R.id.task_name)
TextView taskNameLabel;
@Bind(R.id.checkbox)
CheckBox checkBox;
private Task task;
public TaskRowHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
task.setCompleted(isChecked);
}
});
}
public void setTask(Task task) {
this.task = task;
taskNameLabel.setText(task.getText());
checkBox.setChecked(task.isCompleted());
}
}
TaskAdapter
public class TaskAdapter extends RecyclerView.Adapter<TaskRowHolder> {
private List<Task> tasks = new LinkedList<>();
public void addTask(Task task) {
public TaskRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_task_row, parent, false);
return new TaskRowHolder(itemView);
public void onBindViewHolder(TaskRowHolder holder, int position) {
holder.setTask(tasks.get(position));
public int getItemCount() {
public void removeTask(int position) {
notifyItemRemoved(position);
public class TaskAdapter extends RecyclerView.Adapter<TaskRowHolder> {
private List<Task> tasks = new LinkedList<>();
public void addTask(Task task) {
tasks.add(0, task);
notifyItemInserted(0);
}
@Override
public TaskRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_task_row, parent, false);
return new TaskRowHolder(itemView);
}
@Override
public void onBindViewHolder(TaskRowHolder holder, int position) {
holder.setTask(tasks.get(position));
}
@Override
public int getItemCount() {
return tasks.size();
}
public void removeTask(int position) {
tasks.remove(position);
notifyItemRemoved(position);
}
}
public class TaskAdapter extends RecyclerView.Adapter<TaskRowHolder> {
private List<Task> tasks = new LinkedList<>();
public void addTask(Task task) {
tasks.add(0, task);
notifyItemInserted(0);
}
@Override
public TaskRowHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_task_row, parent, false);
return new TaskRowHolder(itemView);
}
@Override
public void onBindViewHolder(TaskRowHolder holder, int position) {
holder.setTask(tasks.get(position));
}
@Override
public int getItemCount() {
return tasks.size();
}
public void removeTask(int position) {
tasks.remove(position);
notifyItemRemoved(position);
}
}
view_task_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="4dp"
card_view:cardCornerRadius="4dp">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="horizontal"
android:id="@+id/task_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/checkbox"
android:fontFamily="sans-serif-light"
android:textSize="18sp" />
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp" />
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="4dp"
card_view:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:id="@+id/task_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/checkbox"
android:fontFamily="sans-serif-light"
android:textSize="18sp" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="4dp"
card_view:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:id="@+id/task_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/checkbox"
android:fontFamily="sans-serif-light"
android:textSize="18sp" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
Now we need to connect our code to MainActivity.
public class MainActivity extends AppCompatActivity {
private TaskAdapter taskAdapter = new TaskAdapter();
protected void onCreate(Bundle savedInstanceState) {
recyclerView.setAdapter(taskAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext()));
Task newTask = new Task(textInputField.getText().toString());
textInputField.setText("");
taskAdapter.addTask(newTask);
public class MainActivity extends AppCompatActivity {
...
private TaskAdapter taskAdapter = new TaskAdapter();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
recyclerView.setAdapter(taskAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext()));
}
...
@OnClick(R.id.add_task)
public void addTask() {
...
Task newTask = new Task(textInputField.getText().toString());
textInputField.setText("");
taskAdapter.addTask(newTask);
}
}
public class MainActivity extends AppCompatActivity {
...
private TaskAdapter taskAdapter = new TaskAdapter();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
recyclerView.setAdapter(taskAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getBaseContext()));
}
...
@OnClick(R.id.add_task)
public void addTask() {
...
Task newTask = new Task(textInputField.getText().toString());
textInputField.setText("");
taskAdapter.addTask(newTask);
}
}
Result

3.5 Login screen UI
We need some kind of authentication since our data will be connected to particular users. Therefore, in one of the incoming posts we are going to implement the auth logic using Realm (yes, Realm provides this feature :)).
However, we will also need the UI part of login to collect the user data.
A pretty simple layout of the login screen. It will have two fields (username + password) and two actions (register + login).
login_activity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:textColor="@color/colorAccent"
android:text="TaskManager"/>
android:layout_below="@id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingTop="16dp">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test@gmail.com"
android:inputType="textAutoComplete"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeActionId="@+id/log_in"
android:imeActionLabel=""
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:id="@+id/register"
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold" />
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textStyle="bold" />
android:layout_below="@id/form"
android:id="@+id/progress"
android:layout_centerHorizontal="true"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:paddingTop="10dp"
android:gravity="center"
android:textStyle="bold"
android:textColor="@color/colorAccent"
android:text="TaskManager"/>
<LinearLayout
android:layout_below="@id/title"
android:id="@+id/form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingTop="16dp">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test@gmail.com"
android:hint="Login"
android:inputType="textAutoComplete"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test"
android:hint="Password"
android:imeActionId="@+id/log_in"
android:imeActionLabel=""
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/register"
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Register"
android:textStyle="bold" />
<Button
android:id="@+id/login"
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Login"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:layout_below="@id/form"
android:id="@+id/progress"
android:layout_centerHorizontal="true"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:paddingTop="10dp"
android:gravity="center"
android:textStyle="bold"
android:textColor="@color/colorAccent"
android:text="TaskManager"/>
<LinearLayout
android:layout_below="@id/title"
android:id="@+id/form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:orientation="vertical"
android:paddingBottom="10dp"
android:paddingTop="16dp">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test@gmail.com"
android:hint="Login"
android:inputType="textAutoComplete"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test"
android:hint="Password"
android:imeActionId="@+id/log_in"
android:imeActionLabel=""
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/register"
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Register"
android:textStyle="bold" />
<Button
android:id="@+id/login"
style="?android:textAppearanceSmall"
android:layout_weight="0.5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Login"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:layout_below="@id/form"
android:id="@+id/progress"
android:layout_centerHorizontal="true"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
Out Login Screen implementation is also really basic, but we will come back to it later.
LoginActivity
public class LoginActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
private void showMainScreen() {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
public class LoginActivity extends AppCompatActivity {
@Bind(R.id.username)
TextView usernameLabel;
@Bind(R.id.password)
TextView passwordLabel;
@Bind(R.id.progress)
View progressView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
ButterKnife.bind(this);
}
@OnClick(R.id.login)
public void login() {
showMainScreen();
}
private void showMainScreen() {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
@OnClick(R.id.register)
public void register() {
}
}
public class LoginActivity extends AppCompatActivity {
@Bind(R.id.username)
TextView usernameLabel;
@Bind(R.id.password)
TextView passwordLabel;
@Bind(R.id.progress)
View progressView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
ButterKnife.bind(this);
}
@OnClick(R.id.login)
public void login() {
showMainScreen();
}
private void showMainScreen() {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
@OnClick(R.id.register)
public void register() {
}
}
Don’t forget to update AndroidManifest!
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.adriankremski.taskmanager">
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.adriankremski.taskmanager">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
</application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.adriankremski.taskmanager">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
</application>
</manifest>
Result

Thanks for reading!
The next part of the series is already available!
Part 2: REALM MOBILE PLATFORM: OFFLINE-FIRST TASKMANAGER APP – BASICS OF REALM