Share
Explore

Android Pet Farm - Working with Android Kotlin GSON

Create a Virtual Pet Farm app with Kotlin in Android.

Learning outcomes:
-shared preferences,
GSON
This app will allow users to add pets to a store inventory and retrieve the list of pets. Here are the necessary files:
MainActivity.kt
```kotlin package com.example.petstoreapp
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import com.example.petstoreapp.databinding.ActivityMainBinding import com.google.gson.Gson import com.google.gson.reflect.TypeToken
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val sharedPrefFile = "PetStorePrefs"
data class Pet(val name: String, val species: String, val age: Int)
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)
binding.btnAddPet.setOnClickListener { val name = binding.etPetName.text.toString() val species = binding.etPetSpecies.text.toString() val age = binding.etPetAge.text.toString().toIntOrNull() ?: 0
if (name.isNotEmpty() && species.isNotEmpty()) { addPet(Pet(name, species, age)) clearInputFields() Toast.makeText(this, "Pet added successfully", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show() } }
binding.btnShowPets.setOnClickListener { displayPets() } }
private fun addPet(pet: Pet) { val sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE) val editor = sharedPreferences.edit() val gson = Gson()
val petsJson = sharedPreferences.getString("pets", null) val petList = if (petsJson != null) { gson.fromJson<List<Pet>>(petsJson, object : TypeToken<List<Pet>>() {}.type) } else { mutableListOf() }
petList.add(pet) val updatedPetsJson = gson.toJson(petList) editor.putString("pets", updatedPetsJson) editor.apply() }
private fun getPets(): List<Pet> { val sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE) val gson = Gson() val petsJson = sharedPreferences.getString("pets", null) return if (petsJson != null) { gson.fromJson(petsJson, object : TypeToken<List<Pet>>() {}.type) } else { emptyList() } }
private fun displayPets() { val pets = getPets() val petsText = if (pets.isNotEmpty()) { pets.joinToString("\n") { "${it.name} - ${it.species}, ${it.age} years old" } } else { "No pets in the store" } binding.tvPetList.text = petsText }
private fun clearInputFields() { binding.etPetName.text.clear() binding.etPetSpecies.text.clear() binding.etPetAge.text.clear() } } ```
activity_main.xml
```xml <?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:padding="16dp">
<EditText android:id="@+id/etPetName" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Pet Name" />
<EditText android:id="@+id/etPetSpecies" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Pet Species" />
<EditText android:id="@+id/etPetAge" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Pet Age" android:inputType="number" />
<Button android:id="@+id/btnAddPet" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add Pet" />
<Button android:id="@+id/btnShowPets" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Show Pets" />
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="16dp">
<TextView android:id="@+id/tvPetList" android:layout_width="match_parent" android:layout_height="wrap_content" /> </ScrollView>
</LinearLayout> ```
3. AndroidManifest.xml
```xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PetStoreApp" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
</manifest> ```

Build.gradle.kts files:

build.gradle.kts (Project level)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.0.2" apply false
id("com.android.library") version "8.0.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
}
build.gradle.kts (App level)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "com.example.petstoreapp"
compileSdk = 33

defaultConfig {
applicationId = "com.example.petstoreapp"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}

dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.code.gson:gson:2.10.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

This app allows users to:

1. Add pets to the store inventory by entering their name, species, and age. 2. View the list of all pets in the store.
The app uses SharedPreferences to persist the pet data, and GSON to convert the list of Pet objects to JSON for storage and back to objects when retrieving.
To use this app: 1. Create a new Android project in Android Studio. 2. Replace the contents of MainActivity.kt, activity_main.xml, AndroidManifest.xml, and both build.gradle files with the provided code. 3. Build and run the app.
This setup provides a fully functional app demonstrating the use of SharedPreferences and GSON in a pet store context.

megaphone

Trouble-shooting:

AAPT: error: resource style/Theme.PetStoreApp (aka com.example.petstoreapp:style/Theme.PetStoreApp) not found.
This error occurs because the theme "Theme.PetStoreApp" referenced in your AndroidManifest.xml file is not defined in your project. To resolve this:
Open your res/values folder.
Create a new XML file called "themes.xml" if it doesn't exist already.
In themes.xml, define the missing theme:
<resources>
<style name="Theme.PetStoreApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here if needed -->
</style>
</resources>
If you prefer to use an existing theme instead:
Open AndroidManifest.xml
Find the android:theme attribute in the <application> tag
Change it to use a built-in theme, for example:
xml
Copy
android:theme="@style/Theme.MaterialComponents.DayNight.DarkActionBar"
After making either of these changes, rebuild your project. The error should be resolved, and your app should use either the custom theme you defined or the built-in theme you specified.

Troubleshooting MainActivity.kt

To fix the MainActivity.kt and make the application run without errors, we need to address a few issues. Here's the corrected version of MainActivity.kt:


package com.example.petstoreapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.example.petstoreapp.databinding.ActivityMainBinding
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val sharedPrefFile = "PetStorePrefs"

data class Pet(val name: String, val species: String, val age: Int)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.btnAddPet.setOnClickListener {
val name = binding.etPetName.text.toString()
val species = binding.etPetSpecies.text.toString()
val age = binding.etPetAge.text.toString().toIntOrNull() ?: 0

if (name.isNotEmpty() && species.isNotEmpty()) {
addPet(Pet(name, species, age))
clearInputFields()
Toast.makeText(this, "Pet added successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
}
}

binding.btnShowPets.setOnClickListener {
displayPets()
}
}

private fun addPet(pet: Pet) {
val sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
val editor = sharedPreferences.edit()
val gson = Gson()

val petsJson = sharedPreferences.getString("pets", null)
val petList = if (petsJson != null) {
gson.fromJson<MutableList<Pet>>(petsJson, object : TypeToken<MutableList<Pet>>() {}.type)
} else {
mutableListOf()
}

petList.add(pet)
val updatedPetsJson = gson.toJson(petList)
editor.putString("pets", updatedPetsJson)
editor.apply()
}

private fun getPets(): List<Pet> {
val sharedPreferences = getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
val gson = Gson()
val petsJson = sharedPreferences.getString("pets", null)
return if (petsJson != null) {
gson.fromJson(petsJson, object : TypeToken<List<Pet>>() {}.type)
} else {
emptyList()
}
}

private fun displayPets() {
val pets = getPets()
val petsText = if (pets.isNotEmpty()) {
pets.joinToString("\n") { "${it.name} - ${it.species}, ${it.age} years old" }
} else {
"No pets in the store"
}
binding.tvPetList.text = petsText
}

private fun clearInputFields() {
binding.etPetName.text.clear()
binding.etPetSpecies.text.clear()
binding.etPetAge.text.clear()
}
}
The main changes are:
In the addPet function, changed gson.fromJson<List<Pet>> to gson.fromJson<MutableList<Pet>> to ensure we can add to the list.
Made sure all necessary imports are present, especially for Gson and TypeToken.
These changes should resolve the issues and allow the application to run and work as intended. The app will now be able to add pets, display them, and persist the data using SharedPreferences and Gson.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.