This post is part one of two part blog posts where I will provide a step by step tutorial on how to create an Android Notepad App. These two posts are the completion of my post on Android SQLite and Content Provider . If you have not read those two posts, you may want to do so especially if you are new to Android development.
You can get the source code for this Notepad App tutorial from my blog .
At the end of this tutorial, here is what the app will look like.
Screenshot of Notepad App
Now to the training.
Project Creation and User Interface
Step 1: Project Creation– the Android Studio project at this point should be almost blank except for the DatabaseHelper.java file which was added in the SQLite database tutorial and the NoteContentProvider.java that was added in the Content Provider tutorial.
Your Content Provider should look like this Github gist and your DatabaseHelper.java should look like the code below. It is important that your database look similar to this because this will be the foundation upon which we will build out the rest of the app. If your file does not look similar then you may need to get the source code .
public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "simple_note_app.db"; private static final int DATABASE_VERSION = 1; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_NOTE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + Constants.NOTES_TABLE); onCreate(db); } private static final String CREATE_TABLE_NOTE = "create table " + Constants.NOTES_TABLE + "(" + Constants.COLUMN_ID + " integer primary key autoincrement, " + Constants.COLUMN_TITLE + " text not null, " + Constants.COLUMN_CONTENT + " text not null, " + Constants.COLUMN_MODIFIED_TIME + " integer not null, " + Constants.COLUMN_CREATED_TIME + " integer not null" + ")"; }
Dependencies
Please take note that this tutorial was created with Android Studio 1.3.2 and Build Tools 23.0.0 so you may want to update your development environment if it is different to avoid compatibility issues with some of the libraries we will be using.
Step 2: Project Structure– to complete the project structure, add the following packages and files if they do not exist yet.
- At the root of your project add the following packages
- activities
- fragments
- adapters
- models
- data
- utilities
- Move MainActivity.java to the activities package. You do this by right clicking on MainActivity.java and select refactor and then move and then browse/select the package you want to move it to
- Add NoteEditorActivity.java to the activities folder and select the Main Activity as the parent to this Activity
- Add the following Fragments to the fragments package
-
- NoteListFragment.java – this Fragment should extend from
android.support.v4.app.Fragment
and not fromandroid.app.Fragment
expand your import statements after you create the Fragment to make the change - NoteLinedEditorFragment.java – this Fragment should extend from
android.support.v4.app.Fragment
as well - NotePlainEditorFragment.java – this Fragment should extend from
android.support.v4.app.Fragment
too
- NoteListFragment.java – this Fragment should extend from
- Add the following Java class file to the models folder
-
Note.java and here is the initial content for this file, at the top of Android Studio toolbar click on Code -> Generate -> Getter and Setter to apply the boiler-plate getter and setter code.
private Long id; private String title; private String content; private Long dateCreated; private Long dataModified;
-
- Add the following Java class files to the adapter package
- NoteListAdapter.java
- In your utilities package, add a file named Constants.java (this should already exist if you downloaded the source code ), if not add one and the contents of this file should look like this.
public class Constants { public static final String NOTES_TABLE = "notes"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_TITLE = "title"; public static final String COLUMN_CONTENT = "content"; public static final String COLUMN_MODIFIED_TIME = "modified_time"; public static final String COLUMN_CREATED_TIME = "created_time"; public static final String[] COLUMNS = { Constants.COLUMN_ID, Constants.COLUMN_TITLE, Constants.COLUMN_CONTENT, Constants.COLUMN_CREATED_TIME }; }
Your project structure should look like this as we proceed to build this simple Android Notepad App.
Step 3: User Interface– this app will have two user interfaces. One will be a list to display the list of the Notes we have created and the other one will be a screen to actually create the Note. The list screen will be powered by RecyclerView and we will create that in the section for Adapters. For the create/edit Note screen I am going to provide you with two options for the user interface of the screens. One is a plain EditText and the next one is Lined EditText (with lines).
- Under the res/drawable folder add the following two files that will be used to give a little nice looking style to our plain editor screens.
- apptheme_edit_text_holo_light.xml and you can get the content of this file here
- apptheme_textfield_disabled_holo_light.9.png and you can get the file here
- Update your res/values/dimens.xml with this code we will use it to specify the dimensions of view objects in the app instead of hard coding the dimensions.
- Under your res/layout folder, update the layout file fragment_note_plain_editor.xml file to look like the code below. Use the quick fix to enter String values for content and title.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/edit_text_title" android:layout_margin="@dimen/margin_padding_tiny" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/enter_title" android:background="@drawable/apptheme_edit_text_holo_light" android:padding="@dimen/margin_padding_tiny" android:gravity="left" android:singleLine="true" android:textStyle="bold" /> <EditText android:id="@+id/edit_text_note" android:layout_margin="@dimen/margin_padding_tiny" android:layout_width="match_parent" android:layout_weight="1" android:hint="@string/enter_content" android:layout_height="match_parent" android:padding="@dimen/margin_padding_tiny" android:gravity="top" android:background="@drawable/apptheme_edit_text_holo_light" android:inputType="textMultiLine"/> </LinearLayout>
- Notice the inputType attribute set to “textMultiLine” that is what makes the content EditText expand as you type.
- In your models package, add a Java file named LinedEditText.java , this class extends from the Framework EditText and draws lines for the visible editor area. You can find the content of this class here
- Update your res/layout/fragment_note_editor.xml with this code
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="@dimen/margin_padding_tiny" android:paddingRight="@dimen/margin_padding_tiny"> <EditText android:id="@+id/edit_text_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="@dimen/margin_padding_small" android:layout_marginLeft="@dimen/margin_padding_tiny" android:layout_marginTop="@dimen/margin_padding_tiny" android:hint="@string/enter_title" android:padding="@dimen/margin_padding_tiny" android:singleLine="true" android:textColor="#000000"/> <com.okason.simplenoteapp.models.LinedEditText android:id="@+id/edit_text_note" android:layout_width="match_parent" android:layout_height="match_parent" android:capitalize="sentences" android:fadingEdge="vertical" android:gravity="top" android:hint="@string/enter_content" android:inputType="textMultiLine" android:padding="@dimen/margin_padding_small" android:scrollbars="vertical" android:textSize="@dimen/text_size_normal"/> </LinearLayout>
- Notice that we are using LinedEditText instead EditText as the view type for our content element. You should make sure to update the package name to yours and not mine
Step 4: Fragment Container– the user interfaces of our app are presented in Fragments. This allows you to add support for tablets should you wish to continue working on this app beyond this tutorial. Fragments do not have screen real estate of their own so each Fragment has to be contained within within an Activity and a Fragment has to be specifically started before for it to show. Because of this, we need to add a method that opens the Fragments as needed.
First we have to add a container in the Activities layout file that the Fragments will occupy. Update both the activity_main.xml and activity_note_editor.xml files, remove the HelloWorld TextView and add this code instead.
<FrameLayout android:id="@+id/container" android:layout_weight="1" android:padding="@dimen/margin_padding_xtiny" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true" />
Step 5: Open Fragment Method– by default the Activities displays its own user interface defined in their respective xml layout files. So we have to problematically tell the Activity to replace its own UI with that of the Fragments. The act of starting a Fragment from an activity is part of what is called Fragment transaction. The Fragment Manager handles these transactions and many more. In both your MainActivity.java and NoteEditorActivity.java add this method:
private void openFragment(final Fragment fragment, String title){ getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .replace(R.id.container, fragment) .addToBackStack(null) .commit(); getSupportActionBar().setTitle(title); }
Material Design Toolbar
This is 2015 and even the most basic of apps is expected to look decent and it does not take much to add a little flavor to our app. In this section and the next one we will add Floating Action Button, Toolbar and Navigation Drawer to spice up our app.
Step 6: Add Material Design Colors– go to https://www.materialpalette.com/ and choose any color combination of your choice. You will need to choose two colors and then download the XML for that color combination. Add a color.xml file to your res/values folder and copy the contents of the xml you downloaded into the color.xml file that you created and your color.xml file will look like the one below. You need to move the attribution comment under the xml declaration.
<?xml version="1.0" encoding="utf-8"?> <!-- Palette generated by Material Palette - materialpalette.com/red/blue --> <resources> <color name="primary">#F44336</color> <color name="primary_dark">#D32F2F</color> <color name="primary_light">#FFCDD2</color> <color name="accent">#448AFF</color> <color name="primary_text">#212121</color> <color name="secondary_text">#727272</color> <color name="icons">#FFFFFF</color> <color name="divider">#B6B6B6</color> </resources>
Step 7: Add Toolbar– thanks to Chris Banes of the Android Developer Relations team we have a material design toolbar that is compliant with pre Lollipop devices. You can read more about this in Chris blog . In your res/layout folder add a file named toolbar.xml and copy and paste this code into it
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" 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="wrap_content" android:background="?attr/colorPrimary" android:minHeight="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
Step 8: Update Activity Layout Files– update both the MainActivity.java and NoteEditorActivity.java layout files to include the toolbar you just added and then make the container layout to stay below the toolbar like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <include android:id="@+id/toolbar" layout="@layout/toolbar" /> <FrameLayout android:id="@+id/container" android:layout_below="@+id/toolbar" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
Step 9: Update the style file– we need to tell the Framework that we no longer need the Actionbar since we are providing our own custom toolbar. Update your resvaluesstyles.xml file to look like this
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="android:windowActionBar">false</item> <item name="colorPrimary">@color/primary</item> </style> </resources>
Step 10: Add Toolbar in Java Code– we only added the layout of the toolbar defined in XML. We also need to make a programmatic reference to this xml component from Java code before we can use it. For each of our Activities (MainActivity and NoteEditorActivity) add a class instance variable
private Toolbar mToolbar;
. Now within the onCreate() of both Activities, after the call to setContentView add this code
mToolbar = (Toolbar)findViewById(R.id.toolbar); setSupportActionBar(mToolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); //remove this line in the MainActivity.java
And with this we should be able to run the app now in an Emulator or physical device
Step 10: Run the App– at this point you should be able to run the app in the Emulator for testing. Add this code in the MainActivity right after setting the Toolbar. That will take you to the plain editor screen, and after you have tested the plain editor then change the fragment below to instantiate the NoteLinedEditorFragment instead of the NotePlainEditorFragment and run again.
if (savedInstanceState == null){ NotePlainEditorFragment fragment = new NotePlainEditorFragment(); openFragment(fragment, "Note Editor"); }
You apps will look like these screenshots below, If not look at the source code to see what need to be corrected. If that still does not work, please use the comment box to let me know. It will be helpful for you to get this working before we proceed.
Add Material Design Navigation Drawer
Step 11: Add Dependencies– update dependencies section of your build.gradle file (the one that has “Module: app” in parentheses) with the following code to pull in the dependencies for the libraries we will be using. Re-build your project after you add these dependencies.
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.0.0' compile 'com.melnykov:floatingactionbutton:1.3.0' compile 'com.android.support:recyclerview-v7:23.0.0' compile 'com.google.code.gson:gson:2.3.1' compile('com.mikepenz:materialdrawer:4.0.5@aar') { transitive = true } compile 'com.mikepenz:fontawesome-typeface:4.4.0@aar' }
Step 12: Add Navigation Drawer– we will add three items to our navigation drawer, you can add as many items as you see fit if you want to improve on this app. In your MainActivity.java add this variable
private com.mikepenz.materialdrawer.Drawer result = null;
And right below where you set the Toolbar add the following code. Visit the project home for documentation of this library.
//Now build the navigation drawer and pass the AccountHeader result = new DrawerBuilder() .withActivity(this) .withToolbar(mToolbar) .withActionBarDrawerToggle(true) .addDrawerItems( new PrimaryDrawerItem().withName(R.string.title_home).withIcon(FontAwesome.Icon.faw_home).withIdentifier(1), new PrimaryDrawerItem().withName(R.string.title_settings).withIcon(FontAwesome.Icon.faw_list).withIdentifier(2) ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override public boolean onItemClick(View view, int i, IDrawerItem drawerItem) { if (drawerItem != null && drawerItem instanceof Nameable){ String name = ((Nameable)drawerItem).getName().getText(MainActivity.this); mToolbar.setTitle(name); } if (drawerItem != null){ int selectedScren = drawerItem.getIdentifier(); switch (selectedScren){ case 1: //do nothing break; case 2: //go to settings screen, yet to be added //this will be your home work Toast.makeText(MainActivity.this, "Settings Clicked", Toast.LENGTH_SHORT).show(); break; } } return false; } }) .withOnDrawerListener(new Drawer.OnDrawerListener() { @Override public void onDrawerOpened(View view) { KeyboardUtil.hideKeyboard(MainActivity.this); } @Override public void onDrawerClosed(View view) { } @Override public void onDrawerSlide(View view, float v) { } }) .withFireOnInitialOnClick(true) .withSavedInstance(savedInstanceState) .build();
And with that your app should have a Navigation drawer like this one below when you run it. Give it a try and use the comment box below to let me know if it does not work.
Screenshot of Android Notepad App
Floating Action Button
Download this file and add to your res/drawable folder.
Floating action buttons are used for a promoted action. They are distinguished by a circled icon floating above the UI and have motion behaviors that include morphing, launching, and a transferring anchor point.
https://www.google.com/design/spec/components/buttons-floating-action-button.html
We are going to add one to our list of Notes user interface and we will use the popular Makovkastar Floating Action Button Library that we added under the dependencies to easily add a floating action button to our app. In your res/layout/fragment_note_list.xml file, set the root layout type to RelativeLayout instead of Frame Layout add then add the following xml code. Also add this line to the root element of the file
xmlns:fab="http://schemas.android.com/apk/res-auto"
<com.melnykov.fab.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_margin="16dp" android:src="@drawable/ic_add_white_24dp" fab:fab_colorNormal="@color/accent" fab:fab_colorPressed="@color/primary" fab:fab_colorRipple="@color/primary_light" fab:fab_shadow="true" fab:fab_type="normal"/>
Now that we have added the xml layout of Foating Action Button we need to go NoteListFragment.java to make a programmatic association of this xml component to a Java object that we can work with. Update your NoteListFragment with the code below:
public class NoteListFragment extends Fragment { private FloatingActionButton mFab; private View mRootView; public NoteListFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment and hold the reference //in mRootView mRootView = inflater.inflate(R.layout.fragment_note_list, container, false); //Get a programmatic reference to the Floating Action Button mFab = (FloatingActionButton)mRootView.findViewById(R.id.fab); //attach an onClick listener to the Floating Action Button mFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(getActivity(), NoteEditorActivity.class)); } }); return mRootView; } }
What we did in the above code is to get the inflated view of the Fragment and hold a reference to it in an instance variable mRootView. This is necessary because Fragments do not have the method findViewById() so we needed a view that we can use to call findViewById() and that is how we are able to find the Floating Action Button which we stored in the variable mFab.
We then attached an onClick listener to the mFab object and when clicked we started the NoteEditorActivity. This provides another way to go to the NoteEditorActivity since we already added it to the Navigation drawer. At this point, you can remove the Editor entry in the Navigation Drawer.
Run the app to make sure that it is working as expected.
RecyclerView & Adapter
Before the introduction of material design, ListView is the main list renderer in Android and it has served so well. The RecyclerView was introduced as an improved version of ListView, it decouples the list from its container by using layout manager and it provides support for animating common list actions. Follow the steps below to add RecyclerView to the list of Notes.
Step 1: Add RecyclerView Layout – RecyclerView is a type of view and in Android all views are mostly defined in an XML layout file. Then from Java code you can make a programmatic reference to this layout file using findViewById. We need to add RecylerView XML layout to fragment_note_list.xml
Add the following code to fragment_note_list.xml just above the Floating Action Button layout code. You can remove the hello world textview if it is still there.
<!-- A RecyclerView with some commonly used attributes --> <android.support.v7.widget.RecyclerView android:id="@+id/note_recycler_view" android:scrollbars="vertical" android:background="@android:color/transparent" android:layout_width="match_parent" android:layout_height="match_parent"/>
Step 2: Add Custom Row– our list of notes will have two TextView one to display the title of the note and the other one to display the date the note was last modified. Add the layout file row_note_list.xml to your res/layout folder and below is the content of that file
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:padding="@dimen/margin_padding_small" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text_view_note_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:fontFamily="sans-serif-bold" android:gravity="left" android:textSize="@dimen/text_size_xxnormal" /> <TextView android:id="@+id/text_view_note_date" android:layout_width="match_parent" android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:gravity="left" android:textStyle="italic" android:textColor="?android:attr/textColorSecondary" android:textSize="@dimen/text_size_normal" /> </LinearLayout>
Step 4 – Add Human Readable Date Format– the second TextView in the list of notes displays date, update your Note.java class add this method below that displays the date in a human readable format.
public String getReadableModifiedDate(){ SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy - h:mm a", Locale.getDefault()); sdf.setTimeZone(getDataModified().getTimeZone()); Date modifiedDate = getDataModified().getTime(); String displayDate = sdf.format(modifiedDate); return displayDate; }
Step 5: Add RecyclerView Adapter –adapter is what takes the data from the data source and provides that to the list engine to render. Update your NoteListAdaper.java with the following code.
public class NoteListAdapter extends RecyclerView.Adapter<NoteListAdapter.ViewHolder>{ private List<Note> mNotes; private Context mContext; public NoteListAdapter(List<Note> notes, Context context){ mNotes = notes; mContext = context; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View rowView = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_note_list, parent, false); ViewHolder viewHolder = new ViewHolder(rowView); return viewHolder; } @Override public void onBindViewHolder(final ViewHolder holder, int position) { holder.noteTitle.setText(mNotes.get(position).getTitle()); holder.noteCreateDate.setText(mNotes.get(position).getReadableModifiedDate()); } @Override public int getItemCount() { return mNotes.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { public final TextView noteTitle, noteCreateDate; public ViewHolder(View itemView) { super(itemView); noteTitle = (TextView)itemView.findViewById(R.id.text_view_note_title); noteCreateDate = (TextView)itemView.findViewById(R.id.text_view_note_date); } } }
The above adapter is a basic implementation of RecyclerView adapter. In the constructor we pass in the list of notes that we want to display. In the onBindViewHolder() method we use the position parameter of this method to identify the Note we are working with and then we use the name of that Note and Date to set the TextViews.
Step 6: Add Dummy Data– since we have not added data persistence to our app, let us add some sample data to test the RecyclerView that we are adding. In your utilities folder, add a Java class called SampleData.java and you can get the content of this class here .
Step 7: Add RecyclerView Java Code– now we need to use Java code to bring our RecyclerView layout file and the adapter together. Add a method called setupList() to your NoteListFragment.java and then call this method from the onCreateView() method right before you return the view.
private void setupList() { mRecyclerView = (RecyclerView) mRootView.findViewById(R.id.note_recycler_view); mRecyclerView.setHasFixedSize(true); mLayoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(mLayoutManager); final GestureDetector mGestureDetector = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapUp(MotionEvent e) { return true; } }); mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) { View child = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY()); if (child != null && mGestureDetector.onTouchEvent(motionEvent)) { int position = recyclerView.getChildLayoutPosition(child); Note selectedNote = mNotes.get(position); //now we have the selected note } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }); mNotes = SampleData.getSampleNotes(); mAdapter = new NoteListAdapter(mNotes, getActivity()); mRecyclerView.setAdapter(mAdapter); }
That is it, if you run your app, you should see this screen below. This is showing the dummy data that we added. If you are not see the screen below check the source code for comparison. If you do not have the source code you can get it from here.
Summary
In this tutorial we have created the user interface of an Android Notepad app. We added material design toolbar, navigation drawer, floating action button, RecyclerView and RecyclerView.Adapter. The app is showing sample data to demonstrate that what we added is working.
In the next post we will add data persistence to the app and implement CRUD methods. This tutorial has been an excerpt from my book Create Android Notepad App . If you find this tutorial helpful, please use the social media share buttons to share it with anyone who can benefit from it.