What will you learn?
In this simple app, you will learn how to enter a string of text into a file and save it using a button. Next time you open the app, it will still be there.
You will also learn the concept of file handling in Android. File Handling is one of the methods to make your data ‘persist’. This saves your data to a file so that it exists even after the app is closed.
The classes and methods you will learn in this are:
- OutputStreamWriter
- InputStreamReader
- openFileInput()
- openFileOutput()
Note: Find the full code of this scratchpad project towards the end of the post. Read the entire post for further clarification and explanations.
Contents
Building the interface of the scratchpad app in Android
In this app, we’ll be working with a single activity. Let’s move forward to the designing part. As you might have observed earlier, the designing part of an activity is done using XML. Don’t worry if you don’t know XML. Android Studio provides an intuitive drag and drop interface to design app visuals. I’ll be illustrating the code part of it since it is easy to explain in a textual tutorial.
Widgets Required
This is a pretty straightforward app that allows the user to write something down and save it. So we just require two widgets:
- EditText
- Button
- The EditText allows the user to enter text input.
- The Button will be used to save the text in a txt file. If the txt file doesn’t exist, then we will create it.
Creating the layout using XML
Open up the activity_main.xml file, and head over to the Text tab (bottom left corner).
You’ll see the following default code:
<?xml version="1.0" encoding="utf-8"?> <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" tools:context=".MainActivity"> </android.support.constraint.ConstraintLayout>
Now we’ll add the widgets in between the android tag.
First, the EditText. Ideally, it should be big and have lots of space since it is the main part of our app. Write down the following:
<EditText android:id="@+id/todoText" android:layout_width="323dp" android:layout_height="191dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:ems="1" android:textAlignment="center" android:hint="Enter Your Todo Here" android:inputType="textMultiLine" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.503" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.285" />
Now, for the Button, add the following code right after the EditText code.:
<Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="Save" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/todoText" />
We’ve added two widgets: EditText and Button. EditText allows the user to enter text input; it could be anything – email, password, numeric input etc.
Button provides a clickable interface for performing certain actions.
You’ll observe a lot of attributes here, here is a description of what they do:
- constraintBottom, constraintTop etc. : These attributes are specific to elements in a ConstraintLayout. Basically, they are used to set the constraints (boundaries), positioning of elements relative to each other. If you don’t remember the layouts, then it is highly recommended that you read about the different layouts in Android.
- marginLeft, marginRight, etc.: They are used to set the margins for the element.
- layout_width, layout_height: They are used to set the size (height and width) of the element. wrap_content is used to indicate that the size should be adjusted as per the content (text, image, etc.) of the element.
- hint: The hint attribute is used in EditText to add a placeholder. It lets the user know that that’s a clickable field.
- ID: This is used to give a unique identifier to the widget; this is important as it is used to reference these widgets in the Java code. Remember, the Java code has no knowledge of this XML code by default; we need to set it up so that it can recognize elements.
- inputType: Specifies the type of input (email, password, numeric, etc.) in the EditText. We have set the inputType attribute of the EditText to ‘textMultiLine’; this will allow the user to input multiple lines of text.
The end result of the above few lines of code will look like this:
Changing the colours of the App
Additionally, let’s change the colours to the app, these will be visible on the title bar when we run the app, and when the EditText is selected. Head over to res/values/colors.xml.
Replace the default code with the following:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#e5cf22</color> <color name="colorPrimaryDark">#ebb410</color> <color name="colorAccent">#883111</color> </resources>
Coding the actual functionality of our Scratchpad app
Now we’ll get down to the actual coding part. Open up the MainActivity.java file.
Adding widgets to the code
Let’s start adding our widgets to the code. Write the following code inside the main function before the onCreate() function. If you gave it a name other than MainActivity, that will be the main function:
private EditText todoText; private Button saveButton;
Now we’ll reference the actual widgets from the XML file. Write this below setContentView(R.layout.activity_main); inside onCreate() function.
todoText = findViewById(R.id.todoText); saveButton = findViewById(R.id.saveButton);
Remember the IDs we had assigned to the widgets in the XML file? They are used here, to connect the Java code to the XML code components. This will set up our widgets.
How to handle file input and output operations in Android
Now, we’ll define two methods to read and write data to files: readFromFile() and writeToFile().
As the name suggests, readFromFile() will be used to read the data from the file, and writeToFile() will be used to write to the file. If the file doesn’t exist, writeToFile() also creates the file in the memory.
The File that we have been talking about will be a .txt file in private mode so that only this App can access it. The code is as follows:
Writing to a private .txt file in Android
We create a new function and assign it a name. In this particular tutorial, we have given it the name writeToFile(). The code for this goes right after the onCreate() function is closed.
private void writeToFile(String message) { try { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(openFileOutput("todolist.txt", Context.MODE_PRIVATE)); outputStreamWriter.write(message); outputStreamWriter.close(); } catch(FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Reading from a private .txt file in Android
After we have written code for writing to a file, we will write the code to read from the .txt file. This is how it goes down:
private String readFromFile() throws IOException { String result = ""; InputStream inputStream = openFileInput("todolist.txt"); if(inputStream != null) { InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String temp = ""; StringBuilder stringBuilder = new StringBuilder(); while((temp = bufferedReader.readLine()) != null) { stringBuilder.append(temp); stringBuilder.append("\n"); } inputStream.close(); result = stringBuilder.toString(); } return result; }
Code explanation
- The file that we are reading from and writing to is ‘todolist.txt’. The methods openFileInput() and openFileOutput() are used to open the file for reading and writing, respectively.
- The method openFileOutput() has the following signature: public abstract FileOutputStream openFileOutput (String name, int mode), where name is a String indicating the file name and mode is an Integer flag that indicates how the file should be opened. This method opens a file for writing and creates a new file if it doesn’t exist. The different modes are as follows:
MODE_PRIVATE: The default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
MODE_WORLD_READABLE: (Deprecated) Allows all other applications to have read access to the created file.
MODE_WORLD_WRITEABLE: (Deprecated) Allows all other applications to have write access to the created file.
MODE_APPEND: If the file already exists then write data to the end of the existing file instead of erasing it.
- In these methods, we use OutputStreamWriter and InputStreamReader classes, which are used for File I/O. These are predefined classes in Android, and that’s why we first create an object of that class. From documentation:
“An OutputStreamWriter is a bridge from character streams to byte streams: Characters written to it are encoded into bytes using a specified charset. The charset that it uses may be specified by name or maybe given explicitly, or the platform’s default charset may be accepted.
Each invocation of a write() method causes the encoding converter to be invoked on the given character(s). The resulting bytes are accumulated in a buffer before being written to the underlying output stream. The size of this buffer may be specified, but by default, it is large enough for most purposes. Note that the characters passed to the write() methods are not buffered.”
Link: https://developer.android.com/reference/java/io/OutputStreamWriter
“An InputStreamReader is a bridge from byte streams to character streams: It reads bytes and decodes them into characters using a specified charset. The charset that it uses may be specified by name or maybe given explicitly, or the platform’s default charset may be accepted.
Each invocation of one of an InputStreamReader’s read() methods may cause one or more bytes to be read from the underlying byte-input stream. To enable the efficient conversion of bytes to characters, more bytes may be read ahead from the underlying stream than are necessary to satisfy the current read operation.”
Link: https://developer.android.com/reference/java/io/InputStreamReader
- These are the classes used to read from and write to files. To read from files, we use the BufferedReader class to read from files easily, without getting conversion issues.
- The write() method of OutputStreamWriter is used to write to files.
- The readLine() method of BufferedReader is used to read a line of text from the file.
- It is very important to close the Input or Output stream after I/O operations. Hence, the close() button is used to do that.
Implementing the save functionality
Now we’ll implement the saving of text written in the EditText, by clicking the save button.
We’ll also add a Toast message to let the user know that their text has been saved.
A Toast is a small popup that appears at the bottom of the screen for a short duration. Write the following in the onCreate() function in MainActivity.java:
saveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(!todoText.getText().toString().equals("")) { String message = todoText.getText().toString(); writeToFile(message); Toast.makeText(MainActivity.this, "Saved!", Toast.LENGTH_LONG).show(); } } });
setOnClickListener() is used to set up click events. Here, we have used it to call the method for saving the text into the file, whenever the button is clicked.
We need to read from the file as well, whenever the app opens. Write the following in the onCreate() function of MainActivity.java:
try { if(readFromFile() != null) { todoText.setText(readFromFile()); } } catch (IOException e) { e.printStackTrace(); }
The coding part ends here. Now let us test our app.
Testing the Scratchpad App
Let’s now move forward and run the app. Hit the run icon (Green Play button on top). The app will build, and you should see the following:
Type in some text, and hit the save button.
Hit the back button to close the app.
Now, reopen the app (Find the app icon in the app menu on the phone).
Voila! It’s still there.
We have built a fully functional scratchpad app. Go ahead and use it to jot down some notes!
How to view the contents of a private .txt file in Android?
Since we have created the file in Private mode, it cannot be accessed directly. It will not be visible to any other application other than this App. However, if we want to view the contents of this file, we can do that in the command line. The steps are as follows:
- Run the emulator. This is very important since the commands won’t run without a running emulator instance.
- Navigate to the [android-sdk]/platform-tools folder and run the following commands (Replace your-package-name with the package name of your app, it is the first line in MainActivity.java file):
adb shell run-as your-package-name cp -r /data/data/your-package-name /sdcard/ cd files cat todolist.txt
You should now see the contents of the file in your command line window.
Complete Code for the Scratchpad App
activity_main.xml
colors.xml
Java File
Don’t forget to replace the package name with your package name if you are going to copy this entire code!
Android Studio auto-generates most of the code in the beginning. So, you only need to add the additional code. No need to change the already present code.