How to build a simple notepad app for Android

In this post, you are going to learn to make a basic notepad app. This is a great project to get to grips withย because it will lend itself to a range of alternative uses as similar concepts can be used to create SMS apps, email apps and anything that requires text input. This will allow us to look at saving and opening files, as well as working with strings and recycler views, all of which will serve you well in the future.

Before we do anything else, first we need to create a new project. To do this, just open up Android Studio and then select New > New Project. Choose โ€˜Basic Activityโ€™ (the one with the Floating Action Button) and then you should be good to go!

If you open upย content_main.xml using the window on the left, then you should be greeted with a preview of what your app is going to look like (if you canโ€™t see this, then hit the โ€˜Designโ€™ tab along the bottom). The default set-up is a blank screen with a label saying โ€˜Hello Worldโ€™.

In the Preview window, drag that label so that it fills the whole usable screen. Now, in the Text view, change โ€˜TextViewโ€™ into โ€˜EditTextโ€™. Instead of a static label, that view will become a small window where we can type our notes.

Pretty easy so far! But donโ€™t get complacentโ€ฆ

Your XML code should look something like this:

<EditText
 android:layout_width="391dp"
 android:layout_height="523dp"
 android:hint="Type here..."
 android:gravity="top"
 android:id="@+id/EditText1"
 app:layout_constraintTop_toTopOf="parent"/>

Weโ€™ve changed the text and made it a โ€˜hintโ€™ (meaning it is greyed out and will disappear when the user starts inputting text), weโ€™ve fixed the gravity so that the text is aligned along the top and weโ€™ve given our view an ID so that we can find it in our Java code later on.

Give this a try and you should now be able to enter some text as you would like.


Saving files

Next up, we need to give our users the ability toย saveย their notes.

Thereโ€™s not much use in a note-taking app without this feature!

There are a number of options here but in most cases, youโ€™ll want to save your notes internally. That is to say that we arenโ€™t creating text files to store on the SD card where other apps can access them, seeing as most users donโ€™t regularly navigate their file hierarchies the way they do on a Windows PC. That and we wouldnโ€™t want another app spying on our usersโ€™ notes! Thus, we want to use internal storage. This essentially works just the same as writing external files, except the directory is only going to be visible to our app. No other app can access it and the user canโ€™t view the files using a file manager unless they have root. Note that the files in this directory will be destroyed if the user uninstalls and re-installs your app though.

Luckily, this is a very straightforward process and it simply involves getting a reference to a file object and then using aย FileOutputStream. If we donโ€™t define the location for our files, the internal storage will be the default.

And to keep with Googleโ€™s Material Design design language, weโ€™re going to map this action to the FAB. So open up theย activity_main.xmlย (which controls the layout of your activity) and then enter the Design view. Now double click on the FAB in order to view some options on the right. Click the three dots next toย srcCompatย and then search for the save icon.

We want to make something happen when the user clicks the save button too. Fortunately, thatโ€™s pretty easy as Android Studio has already shown us how to do it. Open upย MainActivity.javaย and look for the text that says โ€œReplace with your own actionโ€. Stick whatever you want in here and it will happen whenever the user clicks save. Weโ€™re going to put this code into a method though, so that we can easily re-use it at will. Weโ€™ll call our method โ€˜Saveโ€™ (that seems to make senseโ€ฆ) and we shall make it work as follows:

public void Save(String fileName) {
    try {
        OutputStreamWriter out =
            new OutputStreamWriter(openFileOutput(fileName, 0));
        out.write(EditText1.);
        out.close();
        Toast.makeText(this, "Note Saved!", Toast.LENGTH_SHORT).show();
    } catch (Throwable t) {
        Toast.makeText(this, "Exception: " + t.toString(), Toast.LENGTH_LONG).show();
    }
}

This code will create a new file with the same name as whatever string we pass it. The content of the string will be whatever is in our EditText. That means that we also need to define the EditText, so just above your onCreate method, writeย EditText EditText1; and then somewhere inย theย onCreateย method at some point afterย setContentView, write:ย EditText1 = (EditText)findViewById(R.id.EditText1);. Donโ€™t worry, Iโ€™ll share the full code in a moment.

Remember, when we use certain commands we need to first import the relevant class. If you type something and find it is underlined as an error, click on it and then hit Alt+Enter. This will automatically add the relevantย importย at the top of your code.

We also want to call the newย Saveย method fromย OnCreate, so add:ย Save(โ€œNote1.txtโ€);ย to execute your handiwork. Then hit play.

If youโ€™ve done this all correctly, then hitting save should create a new file in the appโ€™s internal directory. You wonโ€™t be able to see this though, so how do we know itโ€™s worked? Now we need to add a load function!


Loading files

Loading files is done in a similar way to saving them with a few additional requirements. First, we need to check that the file weโ€™re loading actually exists. To do that, weโ€™re going to create a Boolean (true or false variable) that checks to see if the file exists. Place this somewhere in your code, outside of other methods:

public boolean FileExists(String fname){
  File file = getBaseContext().getFileStreamPath(fname);
  return file.exists();
}

Now we can create the followingย Openย method and pass it the file name string that we want to open. It will return the content as a string, so we can do with it as we please. It should look like so:

public String Open(String fileName) {
    String content = "";
    if (FileExists(fileName)) {
        try {
            InputStream in = openFileInput(fileName);
            if ( in != null) {
                InputStreamReader tmp = new InputStreamReader( in );
                BufferedReader reader = new BufferedReader(tmp);
                String str;
                StringBuilder buf = new StringBuilder();
                while ((str = reader.readLine()) != null) {
                    buf.append(str + "\n");
                } in .close();
                content = buf.toString();
            }
        } catch (java.io.FileNotFoundException e) {} catch (Throwable t) {
            Toast.makeText(this, "Exception: " + t.toString(), Toast.LENGTH_LONG).show();
        }
    }
    return content;
}

This reads each line and then builds a string out of them, using the โ€˜\nโ€™ (newline symbol) at the end of each line for basic formatting. Finally, we use this new string to populate ourย EditText1.

Iโ€™m calling thisย Openย function from theย onCreateย method for now, which means the file will show as soon as the app loads. Obviously, this isnโ€™t typical behavior for a notepad app but I quite like it โ€“ it means whatever you write will be instantly visible upon loading โ€“ like a mini scratchpad where you can jot down things you need to remember temporarily!

The full code should look like this:

public class MainActivity extends AppCompatActivity {
    EditText EditText1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Save("Note1.txt");
            }
        });

        EditText1 = (EditText) findViewById(R.id.EditText1);
        EditText1.setText(Open("Note1.txt"));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    public void Save(String fileName) {
        try {
            OutputStreamWriter out =
                new OutputStreamWriter(openFileOutput(fileName, 0));
            out.write(EditText1.getText().toString());
            out.close();
            Toast.makeText(this, "Note saved!", Toast.LENGTH_SHORT).show();
        } catch (Throwable t) {
            Toast.makeText(this, "Exception: " + t.toString(), Toast.LENGTH_LONG).show();
        }
    }

    public String Open(String fileName) {
        String content = "";
        if (FileExists(fileName)) {
            try {
                InputStream in = openFileInput(fileName);
                if ( in != null) {
                    InputStreamReader tmp = new InputStreamReader( in );
                    BufferedReader reader = new BufferedReader(tmp);
                    String str;
                    StringBuilder buf = new StringBuilder();
                    while ((str = reader.readLine()) != null) {
                        buf.append(str + "\n");
                    } in .close();
                    content = buf.toString();
                }
            } catch (java.io.FileNotFoundException e) {} catch (Throwable t) {
                Toast.makeText(this, "Exception: " + t.toString(), Toast.LENGTH_LONG).show();
            }
        }
        return content;
    }

    public boolean FileExists(String fname) {
        File file = getBaseContext().getFileStreamPath(fname);
        return file.exists();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

Try running that again. Write something, save and exit the app. Then pop back in and you should find that the text persists. Success!

Managing notes

So far so good, but in reality most notepad apps should give their users the ability to saveย more than one note. For that, weโ€™re going to need some kind of note select screen!

Right click somewhere in your hierarchy on the left and select New > Activity, then choose โ€˜Basic Activityโ€™ again. Weโ€™re calling this one โ€˜NoteSelectโ€™. Enter that into the Activity Name and then hit โ€˜Finishโ€™.

This will generate your Java file, your content layout and your app layout. Open up theย activity_note_select.xml file and weโ€™re going to make some similar changes to last time. This time, we want our FAB to display a โ€˜newnoteโ€™ icon for creating new notes. Thereโ€™s nothing already available that really satisfies our requirements, so make your own and drop it into your appโ€™s โ€˜drawableโ€™ folder. You can do this by navigating to the project directory, or right clicking on the folder on the left of Android Studio and selecting โ€˜Show in Explorerโ€™. You should now be able to select it from the list as before โ€“ remember that file names in your resources need to be lower case.

Weโ€™re going to use a recycler view in order to display our notes, which makes life a little more complicated. The good news is that using recycler views has become easier since last time (when we built the gallery app). You no longer need to add the dependency to Gradle and now the view can be selected straight from the designer, nice!

So add your recycler view as usual to the notes_select_content.xml and give it the ID โ€˜notesโ€™. The XML code should look like this:

<android.support.constraint.ConstraintLayout 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"
 app:layout_behavior="@string/appbar_scrolling_view_behavior"
 tools:context="com.example.rushd.notepad.NoteSelect"
 tools:layout_editor_absoluteX="0dp"
 tools:layout_editor_absoluteY="81dp"
 tools:showIn="@layout/activity_note_select">

<android.support.v7.widget.RecyclerView
 android:layout_width="0dp"
 android:layout_height="0dp"
 android:clipToPadding="false"
 android:gravity="top"
 tools:layout_editor_absoluteX="8dp"
 tools:layout_editor_absoluteY="8dp"
 tools:layout_constraintTop_creator="1"
 tools:layout_constraintRight_creator="1"
 tools:layout_constraintBottom_creator="1"
 android:layout_marginStart="8dp"
 app:layout_constraintBottom_toBottomOf="parent"
 android:layout_marginEnd="8dp"
 app:layout_constraintRight_toRightOf="parent"
 android:layout_marginTop="8dp"
 tools:layout_constraintLeft_creator="1"
 android:layout_marginBottom="8dp"
 app:layout_constraintLeft_toLeftOf="parent"
 app:layout_constraintTop_toTopOf="parent"
 android:layout_marginLeft="8dp"
 android:layout_marginRight="8dp"
 android:id="@+id/notes"/>
</android.support.constraint.ConstraintLayout>

Next, create a new Java class (weโ€™re ignoring the new activity for now). This Java class is going to build our note object (quick primer on what an object is in programming) so weโ€™ll call it NotesBuilder. Right click on the Java folder and select New > Java Class. Add the following code:

public class NotesBuilder {

private String title,
content;

public NotesBuilder() {
}

public NotesBuilder(String title, String content) {
this.title = title;
this.content = content;
}

public String getTitle() {
return title;
}

public String getContent() {
return content;
}
}
Now we need another new layout file, which is going to define the layout of each row in our recycler view. This will be called list_row.xml and youโ€™ll create it by right clicking on the layout folder and then choose New > Layout resource file. Pick โ€˜Relative Layoutโ€™ in the next dialog that comes up. The great thing about recycler view is that you can be as elaborate here as you like and include images and all kinds of other views in each row. We just want something simple for now though, so it will look like this:


&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"&gt;

&lt;TextView
android:id="@+id/title"
android:textSize="16dp"
android:textStyle="bold"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content" /&gt;

&lt;TextView
android:id="@+id/content"
android:layout_below="@id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" /&gt;

&lt;/RelativeLayout&gt;

Next we need to create an โ€˜adaptorโ€™. Basically, an adaptor takes a data set and attaches it to the recycler view. This will be another new Java class and this one will be called โ€˜NotesAdapterโ€™.

public class NotesAdapter extends RecyclerView.Adapter & lt;
NotesAdapter.MyViewHolder & gt; {

    private List & lt;
    NotesBuilder & gt;
    notesList;

    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView title, content;

        public MyViewHolder(View view) {
            super(view);
            title = (TextView) view.findViewById(R.id.title);
            content = (TextView) view.findViewById(R.id.content);

        }
    }

    public NotesAdapter(List & lt; NotesBuilder & gt; notesList) {
        this.notesList = notesList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.list_row, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        NotesBuilder note = notesList.get(position);
        holder.title.setText(note.getTitle());
        holder.content.setText(note.getContent());
    }

    @Override
    public int getItemCount() {
        return notesList.size();
    }
}

Now if you look over this code, youโ€™ll see that it is going through a list calledย notesListย that has been built with our NoteBuilder class. Now everything is in place, we just need to add the relevant code to the NoteSelect.java script. This will read as follows:

public class NoteSelect extends AppCompatActivity {

    private List & lt;
    NotesBuilder & gt;
    notesList = new ArrayList & lt; & gt;
    ();
    private NotesAdapter nAdapter;
    private RecyclerView notesRecycler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_note_select);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
            }
        });

        notesRecycler = (RecyclerView) findViewById(R.id.notes);

        nAdapter = new NotesAdapter(notesList);
        RecyclerView.LayoutManager mLayoutManager =
            new LinearLayoutManager(getApplicationContext());
        notesRecycler.setLayoutManager(mLayoutManager);
        notesRecycler.setItemAnimator(new DefaultItemAnimator());
        notesRecycler.setAdapter(nAdapter);

        prepareNotes();

    }

    private void prepareNotes() {
        File directory;
        directory = getFilesDir();
        File[] files = directory.listFiles();
        String theFile;
        for (int f = 1; f & lt; = files.length; f++) {
            theFile = "Note" + f + ".txt";
            NotesBuilder note = new NotesBuilder(theFile, Open(theFile));
            notesList.add(note);
        }

    }

    public String Open(String fileName) {
        String content = "";
        try {
            InputStream in = openFileInput(fileName);
            if ( in != null) {
                InputStreamReader tmp = new InputStreamReader( in );
                BufferedReader reader = new BufferedReader(tmp);
                String str;
                StringBuilder buf = new StringBuilder();
                while ((str = reader.readLine()) != null) {
                    buf.append(str + "\n");
                } in .close();

                content = buf.toString();
            }
        } catch (java.io.FileNotFoundException e) {} catch (Throwable t) {
            Toast.makeText(this, "Exception: " + t.toString(), Toast.LENGTH_LONG).show();
        }

        return content;
    }
}

Again, make sure that youโ€™re remembering to import classes as you are prompted to do so.

So whatโ€™s happening here? Well first, we are using aย LinearLayoutManagerย and populating the RecyclerView using the adapter so that it shows our notes.ย prepareNotesย is the method where this happens. Here, we are opening up the internal storage directory and weโ€™re looking through the files. We called the first note we created โ€˜Note1โ€™ and we would follow this nomenclature as we went if we were to build this app further. In other words, the next note would be Note2, Note3 and so on.

So this means we can use aย Forย loop in order to look through the list of files. Each one is then used to populate the list, so that the file name is the title and the content is displayed underneath. To grab the content, Iโ€™m re-using theย Openย method.


Where to go from here

Now in an ideal world, we would place theย Saveย andย Openย methods in a separate Java class and call them from there, but this is an easy way to do it in the interests of brevity.

Likewise, were we going to build this into a full app, weโ€™d probably only want to load the first line of the text file. Weโ€™d likely want to give the user a way to create their own app titles too. Thereโ€™s lots more work to be done here!

But as a starting point, you now have the ability to create, list and load notes. The rest is up to you!

One last tweak: you need to be able to access the list of notes! To do this, add the following code to yourย onOptionsItemSelectedย method in MainActivity and change the value ofย action_settingsย from โ€˜Settingsโ€™ to โ€˜List Notesโ€™ in the strings.xml resource file. While youโ€™re there, change the color codes too to make your app a little less generic.

Now the top right menu will give you the option to โ€˜List Notesโ€™ and tapping that will take you to the list of your notes:

Intent myIntent = new Intent(MainActivity.this, NoteSelect.class);
MainActivity.this.startActivity(myIntent);

We would want to add anย onClickListenerย to our recycler so that hitting a note would do something similar โ€“ starting theย MainActivityย and passing an extra parameter telling the activityย whichย note to load. If the user opted to build a new note using the FAB, then the file name would be the number of files in the internal directoryย +1. Clicking save would then save this file and add it to the list.

Give it a go, have a play around and hopefully inspiration will strike! At the very least, youโ€™ll have a nice note taking app that you can customize to your liking and youโ€™ll have learned some handy skills along the way!