Written by Adrian Kremski
Android Developer
Published January 24, 2017

Realm Mobile Platform: Offline-first TaskManager app – Intro

This article is the first one in a series of five. Each part will describe the steps of building offline-first TaskManager app with realtime synchronization. Part one is devoted to defining the technology stack and the UI of the app.

Part 2: REALM MOBILE PLATFORM: OFFLINE-FIRST TASKMANAGER APP – BASICS OF REALM
Part 3: REALM MOBILE PLATFORM: ANDROID “TASKMANAGER” – REALM OBJECT SERVER
Part 4: REALM MOBILE PLATFORM: OFFLINE-FIRST TASKMANAGER APP – AUTHENTICATION
Part 5: Coming soon

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

apka_final

1. Technology stack

Realm Mobile Platform

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.

Ubuntu 16.04. hosted on Amazon EC2

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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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

Screenshot_20160718-205804

activity_main.xml

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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>
<?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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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>
<?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)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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>
<?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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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); } }
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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>
<?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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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(); } } }
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
nkbewtfk2z

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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; } }
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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()); } }
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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); } }
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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>
<?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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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); } }
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

nkbewtfk2z

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<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>
<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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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() { } }
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!

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<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>
<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

nkbewtfk2z

Thanks for reading!

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

Written by Adrian Kremski
Android Developer
Published January 24, 2017