TwoDo dual-screen data entry sample

Hersh Vakharia

Hello Android developers!

A common “first app” for new developers is a simple to-do list app – it typically incorporates data entry, database storage, multiple views, and simple navigation, so it’s great for learning. The app I’m sharing today, called TwoDo, is exactly that, except it is also built for dual-screen devices like Microsoft Surface Duo. It will ultimately be used in a Microsoft Learn module to show developers how they can use Jetpack Window Manager to create dual-screen app experiences.

App Overview

TwoDo is a basic to-do list app that leverages the dual-screen layout of foldable mobile devices like Surface Duo. The user can view a list of tasks on one screen and create/edit tasks and view their details on the other screen.

Figure 1: Dual-screen to-do list experience

Technical Details

The app has two activities: a splash activity (MainActivity.kt), and the TwoDo activity (TwoDoActivity.kt). The splash activity serves as a “welcome” screen for TwoDo, while the TwoDo activity is the actual to-do list.

Splash Activity – Jetpack WM, MotionLayout, and ReactiveGuide

The splash activity shows how to create a dual-screen app in its simplest form. The welcome message appears normally, but when spanned, a logo appears on the other screen. Jetpack Window Manager is used to detect the fold. When the fold is detected, its position is calculated and sent to the ReactiveGuide, which the logo is constrained to. MotionLayout handles the animation of the logo appearing on the right screen. A more detailed explanation of this technique can be found in a previous blog: ExoPlayer video on dual-screen and foldable devices.


Figure 2: The splash activity separates the welcome message and the logo onto each screen

TwoDo Activity – SlidingPaneLayout and Room

The TwoDo activity leverages SlidingPaneLayout for its dual-screen capabilities and Android Room for storing task data on the device.

The layout associated with this activity, activity_two_do.xml, has a SlidingPaneLayout root view. SlidingPaneLayout is a Jetpack Window Manager feature that provides a horizontal pane layout. This is perfect for many dual-screen applications because it can handle moving between panes in single-screen mode and showing both panes at once when the app is spanned across both screens. SlidingPaneLayout must be provided with two children views – one for each pane. In TwoDo, the first child view is a Relative Layout that contains the title text, button, and a RecyclerView that loads the list of tasks. The second child view is a FragmentContainerView, which loads a create/edit task fragment or a logo fragment if we are not creating or editing a task. The following code snippet is the SlidingPaneLayout xml used in TwoDo.

<androidx.slidingpanelayout.widget.SlidingPaneLayout 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:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TwoDoActivity">

    <RelativeLayout
        android:id="@+id/side_pane_content"
        android:layout_width="400dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:orientation="vertical">

        <TextView
            android:id="@+id/twodo_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@string/task_list_title"
            android:textSize="30sp"
            android:textStyle="bold" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/twodo_title">

        </androidx.recyclerview.widget.RecyclerView>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentStart="true"
            android:layout_margin="@dimen/fab_margin"
            android:backgroundTint="?colorPrimary"
            app:tint="@android:color/white"
            app:srcCompat="@drawable/ic_plus_24" />

    </RelativeLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/task_container"
        android:layout_width="460dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>


Figure 3: TwoDo task list demo

Tasks are stored on the device using Android Room. A task consists of four data fields: uid, name, notes, and complete (boolean). We can create an Entity with these fields with Room, which defines the table and its columns. In this case, our table’s PrimaryKey is the uid, and it is automatically generated by Room as we insert tasks. From there, we create a DAO, or data access object. In the Task DAO, we can define methods that access the database, such as queries for getting tasks, and creating/updating/deleting tasks. Android Room automatically generates the implementation of these methods at compile time. We can ultimately use these implementations for accessing the database through a ViewModel. The TwoDo activity and its associated fragments all use the same ViewModel instance to ensure they are all using the same set of data. The following code snippet is the TaskDao Kotlin file.

@Dao
interface TaskDao {

    @Query("SELECT*FROM twodo_table ORDER BY uid ASC")
    fun getTwoDoList(): LiveData>

    @Query("SELECT*FROM twodo_table WHERE uid=:uid")
    fun getTask(uid: Long): LiveData

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertTask(task: Task)

    @Update
    suspend fun updateTask(task: Task)

    @Delete
    suspend fun deleteTask(task: Task)
}

Feedback and Resources

The code for the TwoDo sample is available on GitHub.

If you have any questions, or would like to tell us about your apps, use the feedback forum or message us on Twitter @surfaceduodev. You can read more about Jetpack Window Manager and other samples in our past blog posts.

Finally, please join us for our dual screen developer livestream at 11am (Pacific time) each Friday – mark it in your calendar and check out the archives on YouTube.

0 comments

Discussion is closed.

Feedback usabilla icon