Project structure:
[Link] (project)
// Top-level build file where you can add configuration options common to
all sub-projects/modules.
plugins {
alias([Link]) apply false
alias([Link]) apply false
}
[Link] (App)
plugins {
alias([Link])
alias([Link])
}
android {
namespace = "[Link]"
compileSdk = 34
defaultConfig {
applicationId = "[Link]"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner =
"[Link]"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("[Link]"),
"[Link]"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
// Firebase BoM (manage versions automatically)
implementation(platform("[Link]:firebase-bom:34.4.0"))
// Firebase Authentication
implementation("[Link]:firebase-auth-ktx")
// Firebase Firestore
implementation("[Link]:firebase-firestore-ktx")
// Kotlin Coroutines (for async operations)
implementation("[Link]:kotlinx-coroutines-
android:1.7.3")
implementation([Link])
implementation([Link])
implementation([Link])
implementation([Link])
implementation([Link])
implementation([Link])
implementation([Link])
testImplementation([Link])
androidTestImplementation([Link])
androidTestImplementation([Link])
}
Main Activity
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class MainActivity : AppCompatActivity() {
private val repo = FirebaseRepository()
private val adapter = NotesAdapter(mutableListOf())
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
setContentView([Link].activity_main)
val btnSignOut = findViewById<Button>([Link])
val btnAdd = findViewById<Button>([Link])
val etNote = findViewById<EditText>([Link])
val rvNotes = findViewById<RecyclerView>([Link])
[Link] = LinearLayoutManager(this)
[Link] = adapter
[Link] {
[Link]()
startActivity(Intent(this, LoginActivity::[Link]))
finish()
}
[Link] {
val text = [Link]().trim()
if ([Link]()) return@setOnClickListener
CoroutineScope([Link]).launch {
val res = [Link](text)
withContext([Link]) {
if ([Link]) {
[Link]()
loadNotes()
} else {
[Link](
this@MainActivity,
[Link]()?.localizedMessage ?: "Add
failed",
Toast.LENGTH_LONG
).show()
}
}
}
}
loadNotes()
}
private fun loadNotes() {
CoroutineScope([Link]).launch {
val res = [Link]()
withContext([Link]) {
if ([Link]) {
[Link]([Link](emptyList()))
} else {
[Link](
this@MainActivity,
[Link]()?.localizedMessage ?: "Load
failed",
Toast.LENGTH_LONG
).show()
}
}
}
}
}
/* Minimal RecyclerView Adapter kept in same file for simplicity.
Move to its own file if you prefer cleaner structure. */
class NotesAdapter(private val items: MutableList<Note>) :
[Link]<[Link]>() {
inner class VH(val v: [Link]) : [Link](v)
override fun onCreateViewHolder(parent: [Link],
viewType: Int): VH {
val v = [Link]([Link])
.inflate([Link].item_note, parent, false)
return VH(v)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val note = items[position]
val tv =
[Link]<[Link]>([Link])
[Link] = [Link]
[Link] {
// optional: delete note on long press
val id = [Link]
if ([Link]()) {
CoroutineScope([Link]).launch {
val repo = FirebaseRepository()
val res = [Link](id)
withContext([Link]) {
if ([Link]) {
[Link](position)
notifyItemRemoved(position)
} else {
[Link](
[Link],
[Link]()?.localizedMessage ?:
"Delete failed",
Toast.LENGTH_LONG
).show()
}
}
}
}
true
}
}
override fun getItemCount(): Int = [Link]
fun setItems(newItems: List<Note>) {
[Link]()
[Link](newItems)
notifyDataSetChanged()
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[Link]
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btnSignOut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sign Out" />
<EditText
android:id="@+id/etNote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter note"
android:layout_marginTop="12dp"
android:inputType="text" />
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Note"
android:layout_marginTop="8dp" />
<[Link]
android:id="@+id/rvNotes"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
android:layout_weight="1"
android:scrollbars="vertical" />
</LinearLayout>
login activity
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class LoginActivity : AppCompatActivity() {
private val repo = FirebaseRepository()
override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
setContentView([Link].activity_login)
val etEmail = findViewById<EditText>([Link])
val etPassword = findViewById<EditText>([Link])
val btnLogin = findViewById<Button>([Link])
val tvRegister = findViewById<TextView>([Link])
[Link] {
val email = [Link]().trim()
val pass = [Link]().trim()
if ([Link]() || [Link]()) {
[Link](this, "Enter email and password",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
CoroutineScope([Link]).launch {
val res = [Link](email, pass)
withContext([Link]) {
if ([Link]) {
startActivity(Intent(this@LoginActivity,
MainActivity::[Link]))
finish()
} else {
[Link](
this@LoginActivity,
[Link]()?.localizedMessage ?:
"Login failed",
Toast.LENGTH_LONG
).show()
}
}
}
}
[Link] {
startActivity(Intent(this, RegisterActivity::[Link]))
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[Link]
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"
android:layout_marginBottom="12dp" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login" />
<TextView
android:id="@+id/tvRegister"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Don't have an account? Register"
android:textColor="@android:color/holo_blue_dark"
android:layout_marginTop="20dp" />
</LinearLayout>
register activity
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class RegisterActivity : AppCompatActivity() {
private val repo = FirebaseRepository()
override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
setContentView([Link].activity_register)
val etEmail = findViewById<EditText>([Link])
val etPassword = findViewById<EditText>([Link])
val etConfirmPassword =
findViewById<EditText>([Link])
val btnRegister = findViewById<Button>([Link])
val tvLogin = findViewById<TextView>([Link])
[Link] {
val email = [Link]().trim()
val password = [Link]().trim()
val confirmPassword = [Link]().trim()
if ([Link]() || [Link]() ||
[Link]()) {
[Link](this, "All fields are required",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (password != confirmPassword) {
[Link](this, "Passwords do not match",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
CoroutineScope([Link]).launch {
val result = [Link](email, password)
withContext([Link]) {
if ([Link]) {
[Link](this@RegisterActivity, "Registration
successful", Toast.LENGTH_SHORT).show()
startActivity(Intent(this@RegisterActivity,
LoginActivity::[Link]))
finish()
} else {
[Link](
this@RegisterActivity,
[Link]()?.localizedMessage ?:
"Registration failed",
Toast.LENGTH_LONG
).show()
}
}
}
}
[Link] {
startActivity(Intent(this, LoginActivity::[Link]))
finish()
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[Link]
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Register"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"
android:layout_marginBottom="12dp" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"
android:layout_marginBottom="12dp" />
<EditText
android:id="@+id/etConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Confirm Password"
android:inputType="textPassword"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnRegister"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Register" />
<TextView
android:id="@+id/tvLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Already have an account? Login"
android:textColor="@android:color/holo_blue_dark"
android:layout_marginTop="20dp" />
</LinearLayout>
[Link]:
package [Link]
data class Note(
var id: String = "",
var text: String = ""
)