Topic: 13 Understanding Data Storage in Android
This is our thirteenth topic from learn android from basic to advance series
Hello devs, Today's topic is Data storage in Android. Every time we didn't store data on a server. Sometimes we need to store the data Locally. There are several options for data storage depending on the requirements of your application like storing in shared preference, Internal Storage, External Storage, and many more. Let's Explore one by one.
Shared Preferences
Shared Preferences are used for storing small pieces of data. It allows you to save key-value pairs in a file that persevere even if the application is closed. This makes it ideal for storing simple user preferences, settings, and other lightweight data.
Imagine you're building a notes app and you want to allow users to choose their preferred theme color. Shared Preferences would be perfect for storing this user preference.
- Initialization:
val sharedPreferences = getSharedPreferences("MyAppPreferences", Context.MODE_PRIVATE)
- Saving Data:
val editor = sharedPreferences.edit()
editor.putString("theme_color", "#FF5722") // Assuming the theme color is orange
editor.apply()
- Retrieving Data:
val themeColor = sharedPreferences.getString("theme_color", "#000000") // Default color is black
In this Kotlin version:
getSharedPreferences()
retrieves the SharedPreferences object just like in Java.edit()
method creates a SharedPreferences.Editor instance for making modifications.putString()
method stores the chosen theme color with the key "theme_color".apply()
method commits the changes asynchronously.When retrieving,
getString()
method retrieves the theme color from SharedPreferences. If the key doesn't exist, it returns the default value "#000000" (black).
Internal Storage
Internal Storage is a way to store private data on the device's internal memory. This storage is private to your application and other apps cannot access it. It's suitable for storing sensitive information or data that only your app needs to access.
Here's a detailed explanation along with a short Kotlin example:
Understanding Internal Storage:
Internal Storage provides a private filesystem for your app. Files stored in internal storage are accessible only to your app and cannot be accessed by other apps or users. This makes it ideal for storing sensitive user data like login credentials, cached files, or any other private data your app needs.
Example Usage:
Let's say you're developing a notes app and you want to save the user's notes locally on the device's internal storage.
// Function to save note to internal storage
fun saveNoteToInternalStorage(note: String) {
try {
// Open a file named "notes.txt" in private mode
val fileOutputStream = openFileOutput("notes.txt", Context.MODE_PRIVATE)
// Write the note string to the file
fileOutputStream.write(note.toByteArray())
// Close the file output stream
fileOutputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
// Function to read note from internal storage
fun readNoteFromInternalStorage(): String {
var note = ""
try {
// Open the file named "notes.txt"
val fileInputStream = openFileInput("notes.txt")
// Read the contents of the file into a ByteArray
val byteArray = ByteArray(fileInputStream.available())
fileInputStream.read(byteArray)
// Convert the ByteArray to a String
note = String(byteArray)
// Close the file input stream
fileInputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
return note
}
In this example:
openFileOutput()
method creates a FileOutputStream to write data to a file named "notes.txt" in the app's private internal storage. We useMODE_PRIVATE
to ensure that the file is accessible only to our app.write()
method writes the note string to the file.openFileInput()
method creates a FileInputStream to read data from the "notes.txt" file.read()
method reads the contents of the file into a ByteArray.We then convert the ByteArray to a String and return the note.
By using Internal Storage, your app can securely store sensitive user data without exposing it to other apps or users. However, remember that Internal Storage has limited space, so it's best suited for small to moderate amounts of data. If your app needs to store larger amounts of data, you might consider using External Storage or a local database instead.
External Storage
External Storage in Android refers to the storage that's accessible to both the user and other apps. It includes the device's shared external storage (such as an SD card) that can be removed by the user. This storage is suitable for storing large files that need to be shared across different applications or accessible to the user directly.
Here's a detailed explanation along with a short Kotlin example:
Understanding External Storage:
External Storage provides a shared filesystem that can be accessed by both your app and other apps on the device. It's typically used for storing files that need to be shared between apps or accessed by the user, such as photos, videos, or documents. Unlike Internal Storage, External Storage is accessible to other apps and users, so it's important to be mindful of security and permissions when using it.
Example Usage:
Let's say you're building a photo-sharing app and you want to allow users to save photos to their device's external storage.
// Function to save photo to external storage
fun savePhotoToExternalStorage(context: Context, photoBitmap: Bitmap): Boolean {
return try {
// Get the external storage directory
val externalStorageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
// Create a file named "my_photo.jpg" in the Pictures directory
val file = File(externalStorageDir, "my_photo.jpg")
// Create a FileOutputStream to write data to the file
val fileOutputStream = FileOutputStream(file)
// Compress the bitmap to JPEG format and write it to the FileOutputStream
photoBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
// Close the FileOutputStream
fileOutputStream.close()
// Return true indicating success
true
} catch (e: Exception) {
e.printStackTrace()
// Return false indicating failure
false
}
}
// Function to check if external storage is available for read and write operations
fun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
In this example:
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY
_PICTURES)
retrieves the directory where pictures should be stored on the external storage.We create a file named "my_photo.jpg" in the Pictures directory.
We create a FileOutputStream to write data to the file and then compress the Bitmap image to JPEG format and write it to the FileOutputStream.
isExternalStorageWritable()
checks if the external storage is available for read and write operations by checking the state of the external storage.
Using External Storage allows your app to store large files that need to be shared between apps or accessed by the user. However, it's important to handle permissions properly and be aware that external storage may not always be available (e.g., if the user removes the SD card). Additionally, be mindful of the limited space on external storage and avoid storing large amounts of data unnecessarily.
SQLite Database
SQLite Database is a lightweight, embedded relational database management system that is widely used in Android app development for storing structured data. It provides a powerful and efficient way to manage data locally within an Android application. SQLite databases are self-contained, meaning they require minimal configuration and can be easily included with an Android app without requiring a separate server or setup.
Here's a detailed explanation along with a short Kotlin example:
Understanding SQLite Database:
SQLite is a relational database management system that is implemented as a C library. In Android development, SQLite is used to store structured data in a local database file that resides on the device. It supports standard SQL syntax and provides features like transactions, indexes, and triggers.
Example Usage:
Let's say you're developing a simple task management app and you want to store tasks in a SQLite database.
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
// Define the database schema
object TaskContract {
const val TABLE_NAME = "tasks"
const val COLUMN_ID = "_id"
const val COLUMN_TASK_NAME = "task_name"
}
// Create a helper class to manage database creation and version management
class TaskDBHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
const val DATABASE_NAME = "tasks.db"
const val DATABASE_VERSION = 1
}
override fun onCreate(db: SQLiteDatabase?) {
// Create the tasks table
db?.execSQL(
"CREATE TABLE ${TaskContract.TABLE_NAME} (" +
"${TaskContract.COLUMN_ID} INTEGER PRIMARY KEY AUTOINCREMENT," +
"${TaskContract.COLUMN_TASK_NAME} TEXT NOT NULL)"
)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// Drop the tasks table if it exists
db?.execSQL("DROP TABLE IF EXISTS ${TaskContract.TABLE_NAME}")
onCreate(db)
}
}
// Function to add a new task to the database
fun addTask(context: Context, taskName: String): Long {
val dbHelper = TaskDBHelper(context)
val db = dbHelper.writableDatabase
// Create a ContentValues object to store the task data
val values = ContentValues().apply {
put(TaskContract.COLUMN_TASK_NAME, taskName)
}
// Insert the new task into the database
val newRowId = db.insert(TaskContract.TABLE_NAME, null, values)
// Close the database connection
db.close()
return newRowId
}
// Function to retrieve all tasks from the database
fun getAllTasks(context: Context): ArrayList<String> {
val dbHelper = TaskDBHelper(context)
val db = dbHelper.readableDatabase
val cursor = db.query(
TaskContract.TABLE_NAME,
arrayOf(TaskContract.COLUMN_TASK_NAME),
null,
null,
null,
null,
null
)
val tasks = ArrayList<String>()
// Iterate over the cursor and add tasks to the list
while (cursor.moveToNext()) {
val taskName = cursor.getString(cursor.getColumnIndex(TaskContract.COLUMN_TASK_NAME))
tasks.add(taskName)
}
// Close the cursor and database connection
cursor.close()
db.close()
return tasks
}
In this example:
We define a contract class
TaskContract
to define the table and column names.We create a helper class
TaskDBHelper
that extendsSQLiteOpenHelper
to manage database creation and version management.In the
onCreate()
method ofTaskDBHelper
, we define the SQL statement to create the tasks table.We implement functions
addTask()
andgetAllTasks()
to insert new tasks into the database and retrieve all tasks from the database, respectively.Inside these functions, we obtain a writable or readable database instance using
writableDatabase
orreadableDatabase
methods, respectively.We use
ContentValues
to insert data into the database andCursor
to retrieve data from the database.
Using SQLite Database allows your app to store and manage structured data efficiently. It's well-suited for scenarios where you need to store relational data or large datasets locally within your app. However, for more complex data management requirements or scenarios involving concurrent access from multiple threads, you may need to consider using other persistence solutions like Room Persistence Library or Firebase Realtime Database.
Room Database
Room is a persistence library provided by Google as part of the Android Jetpack suite. It's built on top of the SQLite database and provides an abstraction layer over raw SQLite queries. Room simplifies database interactions by providing compile-time checks for SQL queries and LiveData support for observing database changes. It's designed to be efficient, type-safe, and easy to use, making it a preferred choice for managing app data locally on Android.
Here's a detailed explanation along with a short Kotlin example:
Understanding Room Database:
The room consists of three main components: entities, DAOs (Data Access Objects), and the database itself.
Entity: Annotated Kotlin data class representing a table in the database.
DAO (Data Access Object): Interface containing methods for interacting with the database. Each method corresponds to a database operation (e.g., insert, update, delete).
Database: Abstract class extending
RoomDatabase
that defines the database and its version and provides access to DAOs.
Example Usage:
Let's continue with the task management app example and refactor it to use Room for managing tasks.
import androidx.room.*
// Define the Task entity
@Entity(tableName = "tasks")
data class Task(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "task_name")
val taskName: String
)
// Define the TaskDAO interface
@Dao
interface TaskDao {
@Insert
fun insertTask(task: Task)
@Query("SELECT * FROM tasks")
fun getAllTasks(): List<Task>
}
// Define the TaskDatabase abstract class
@Database(entities = [Task::class], version = 1)
abstract class TaskDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}
In this example:
We define a
Task
entity class using annotations provided by Room. The@Entity
annotation specifies the table name, and the@PrimaryKey
annotation specifies the primary key column.We define a
TaskDao
interface containing methods for database operations. We use annotations like@Insert
and@Query
to define insert and select queries, respectively.We define a
TaskDatabase
abstract class extendingRoomDatabase
. We annotate it with@Database
specifying the entities and the database version.Room automatically generates the implementation for
TaskDao
at compile time.
Now, let's use Room in our app to insert and retrieve tasks:
// Function to add a new task to the database using Room
fun addTask(context: Context, taskName: String) {
val task = Task(taskName = taskName)
val db = Room.databaseBuilder(context, TaskDatabase::class.java, "task-db").build()
db.taskDao().insertTask(task)
}
// Function to retrieve all tasks from the database using Room
fun getAllTasks(context: Context): List<Task> {
val db = Room.databaseBuilder(context, TaskDatabase::class.java, "task-db").build()
return db.taskDao().getAllTasks()
}
In this example, we use Room's database builder to create an instance of the database (TaskDatabase
). We then use the DAO (TaskDao
) to perform database operations like insertion and retrieval of tasks.
Using Room Database simplifies database operations by providing compile-time checks and reducing boilerplate code. It also offers support for LiveData, which makes it easy to observe changes in the database and update UI accordingly. Room is a powerful and efficient persistence library that helps in building robust and maintainable Android applications.
Network storage
Network storage, in the context of Android development, refers to the storage of data on remote servers or cloud-based storage solutions accessible over a network, typically the Internet. This enables apps to store and retrieve data from remote locations, providing scalability, accessibility, and data persistence across multiple devices.
Here's a detailed explanation along with a short Kotlin example:
Understanding Network Storage:
Network storage solutions allow Android apps to store data remotely, enabling seamless access to data across different devices and platforms. This includes various cloud storage providers such as Google Drive, Dropbox, Amazon S3, Firebase Cloud Storage, and custom server-based solutions. By leveraging network storage, apps can store large amounts of data, such as user files, media assets, user preferences, and application state, without relying solely on local device storage.
Example Usage:
Let's consider an example where you're developing a file-sharing app that allows users to upload and download files from a cloud storage service like Firebase Cloud Storage.
import com.google.firebase.ktx.Firebase import com.google.firebase.storage.ktx.storage // Function to upload a file to Firebase Cloud Storage fun uploadFileToFirebaseStorage(context: Context, fileUri: Uri, fileName: String) { val storageRef = Firebase.storage.reference val fileReference = storageRef.child("uploads/$fileName") val uploadTask = fileReference.putFile(fileUri) uploadTask.addOnSuccessListener { // File uploaded successfully Toast.makeText(context, "File uploaded successfully", Toast.LENGTH_SHORT).show() }.addOnFailureListener { exception -> // Handle errors Toast.makeText(context, "Error uploading file: ${exception.message}", Toast.LENGTH_SHORT).show() } } // Function to download a file from Firebase Cloud Storage fun downloadFileFromFirebaseStorage(context: Context, fileName: String, localFile: File) { val storageRef = Firebase.storage.reference val fileReference = storageRef.child("uploads/$fileName") val downloadTask = fileReference.getFile(localFile) downloadTask.addOnSuccessListener { // File downloaded successfully Toast.makeText(context, "File downloaded successfully", Toast.LENGTH_SHORT).show() }.addOnFailureListener { exception -> // Handle errors Toast.makeText(context, "Error downloading file: ${exception.message}", Toast.LENGTH_SHORT).show() } }
Network storage, in the context of Android development, refers to the storage of data on remote servers or cloud-based storage solutions accessible over a network, typically the internet. This enables apps to store and retrieve data from remote locations, providing scalability, accessibility, and data persistence across multiple devices.
Here's a detailed explanation along with a short Kotlin example:
Understanding Network Storage:
Network storage solutions allow Android apps to store data remotely, enabling seamless access to data across different devices and platforms. This includes various cloud storage providers such as Google Drive, Dropbox, Amazon S3, Firebase Cloud Storage, and custom server-based solutions. By leveraging network storage, apps can store large amounts of data, such as user files, media assets, user preferences, and application state, without relying solely on local device storage.
Example Usage:
Let's consider an example where you're developing a file-sharing app that allows users to upload and download files from a cloud storage service like Firebase Cloud Storage.
kotlinCopy codeimport com.google.firebase.ktx.Firebase
import com.google.firebase.storage.ktx.storage
// Function to upload a file to Firebase Cloud Storage
fun uploadFileToFirebaseStorage(context: Context, fileUri: Uri, fileName: String) {
val storageRef = Firebase.storage.reference
val fileReference = storageRef.child("uploads/$fileName")
val uploadTask = fileReference.putFile(fileUri)
uploadTask.addOnSuccessListener {
// File uploaded successfully
Toast.makeText(context, "File uploaded successfully", Toast.LENGTH_SHORT).show()
}.addOnFailureListener { exception ->
// Handle errors
Toast.makeText(context, "Error uploading file: ${exception.message}", Toast.LENGTH_SHORT).show()
}
}
// Function to download a file from Firebase Cloud Storage
fun downloadFileFromFirebaseStorage(context: Context, fileName: String, localFile: File) {
val storageRef = Firebase.storage.reference
val fileReference = storageRef.child("uploads/$fileName")
val downloadTask = fileReference.getFile(localFile)
downloadTask.addOnSuccessListener {
// File downloaded successfully
Toast.makeText(context, "File downloaded successfully", Toast.LENGTH_SHORT).show()
}.addOnFailureListener { exception ->
// Handle errors
Toast.makeText(context, "Error downloading file: ${exception.message}", Toast.LENGTH_SHORT).show()
}
}
In this example:
We use Firebase Cloud Storage as the network storage solution.
uploadFileToFirebaseStorage()
function uploads a file from the device's local storage to Firebase Cloud Storage.downloadFileFromFirebaseStorage()
function downloads a file from Firebase Cloud Storage to the device's local storage.Firebase provides SDKs and libraries that simplify interacting with cloud storage services, handling authentication, security, and network operations.
Example Usage with Retrofit:
Retrofit is a type-safe HTTP client for Android and Java that simplifies network communication by allowing you to define REST APIs as interfaces. It handles the network operations in a background thread and automatically serializes and deserializes JSON data.
Let's consider a simple example where we have an API endpoint to fetch user data:
First, add Retrofit to your project. In your app's
build.gradle
file, add the Retrofit dependency:implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Now, let's define an API interface and set up Retrofit to make network requests:
import retrofit2.Call import retrofit2.http.GET // Define data model data class User(val id: Int, val name: String, val email: String) // Define Retrofit interface for API endpoints interface ApiService { @GET("/users") fun getUsers(): Call<List<User>> } // Set up Retrofit instance val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build() // Create ApiService instance val apiService = retrofit.create(ApiService::class.java) // Make network request to fetch users val call = apiService.getUsers() call.enqueue(object : Callback<List<User>> { override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) { if (response.isSuccessful) { val users = response.body() // Process retrieved users } else { // Handle error } } override fun onFailure(call: Call<List<User>>, t: Throwable) { // Handle failure } })
In this example:
We define a data class
User
to represent user data received from the API.We define an interface
ApiService
with a method to fetch users using the@GET
annotation.We set up a Retrofit instance with a base URL and added a GSON converter factory to handle JSON serialization and deserialization.
We create an instance of the
ApiService
using the Retrofit instance.We make an asynchronous network request using
enqueue()
method, and handle the response in callbacksonResponse()
andonFailure()
.
By using Retrofit, we can easily interact with remote APIs to store or retrieve data from network storage. Retrofit simplifies network operations and provides a clean and concise way to define and consume RESTful APIs in Android applications.
Alright devs, It's time to wrap up this blog. I hope this blog helps you to understand Data storage in Android. Ok then we meet on our next blog Types of Functions In Android.
Connect with Me:
Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? π You can also follow my Instagram page for more mobile development-related content. π²π¨βπ» Letβs stay connected, share knowledge and have some fun in the exciting world of app development! π