Aplicación NotesApp en Kotlin
App en Kotlin que se comunica con el API Rest de notas creado en el VPS laravel.alumno.me
¿Esquema de la red?
Aplicación en Kotlin
Crear el proyecto NotesAppWeb
AndroidManifest.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">
<uses-permission android:name="android.permission.INTERNET" />
<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.NotesAppWeb"
tools:targetApi="31">
<activity
android:name=".LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".RegisterActivity"
android:exported="false"
android:windowSoftInputMode="adjustNothing"
android:parentActivityName=".LoginActivity">
</activity>
<activity
android:name=".MainActivity"
android:exported="false"
android:parentActivityName=".LoginActivity">
</activity>
</application>
</manifest>
lib.versions.toml
[versions]
agp = "8.9.3"
kotlin = "2.0.21"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
datastorePreferences = "1.1.7"
kotlinxCoroutinesCore = "1.10.2"
kotlinxCoroutinesAndroid = "1.10.2"
lifecycleCommonJava11 = "2.9.0"
okhttp = "4.12.0"
retrofit = "3.0.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleCommonJava11" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
buid.gradle (del módulo)
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.example.notesappweb"
compileSdk = 35
defaultConfig {
applicationId = "com.example.notesappweb"
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.datastore.preferences)
implementation (libs.androidx.lifecycle.viewmodel.ktx)
// implementation ("com.squareup.retrofit2:retrofit:3.0.0")
// implementation ("com.squareup.retrofit2:converter-gson:3.0.0")
implementation(libs.okhttp)
implementation (libs.retrofit)
implementation (libs.converter.gson)
implementation(libs.kotlinx.coroutines.core)
implementation (libs.kotlinx.coroutines.android)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
model/Login
data class Login(
val token: String,
val name: String
)
model/LoginResponse
data class LoginResponse(
@SerializedName("success")
var success: Boolean,
@SerializedName("data")
var login: Login,
@SerializedName("message")
var message: String
)
model/Note
data class Note(
@SerializedName("id")
val id: Int,
@SerializedName("title")
val title: String,
@SerializedName("description")
val description: String,
@SerializedName("user_id")
val user_id: Int,
/*
@SerializedName("created_at")
val created_at: Timestamp,
@SerializedName("updated_at")
val updated_at: Timestamp
*/
) : Serializable
model/NoteResponse
data class NoteResponse(
@SerializedName("success")
var success: Boolean,
@SerializedName("data")
var note: Note?,
@SerializedName("message")
var message: String
)
model/NotesResponse
data class NotesResponse(
@SerializedName("success")
var success: Boolean,
@SerializedName("data")
var notes: ArrayList<Note>,
@SerializedName("message")
var message: String
)
network/ApiService
interface ApiService {
@POST("api/login")
@FormUrlEncoded
suspend fun login(
@Field("email") email: String,
@Field("password") password: String
) : Response<LoginResponse>
@POST("api/register")
@FormUrlEncoded
suspend fun register(
@Field("name") name: String,
@Field("email") email: String,
@Field("password") password: String,
@Field("confirm_password") confirmPassword: String
) : Response<LoginResponse>
@POST("api/logout")
suspend fun logout() : Response<LogoutResponse>
@Headers(
"Accept: application/json",
)
@GET("api/note")
suspend fun getNotes(): Response<NotesResponse>
@Headers(
"Accept: application/json",
)
@POST("api/note")
@FormUrlEncoded
suspend fun addNote(
@Field("title") title: String,
@Field("description") description: String
): Response<NoteResponse>
@Headers(
"Accept: application/json",
)
@PUT("api/note/{id}")
@FormUrlEncoded
suspend fun updateNote(
@Path("id") id: Int,
@Field("title") title: String,
@Field("description") description: String
): Response<NoteResponse>
@Headers(
"Accept: application/json",
)
@DELETE("api/note/{id}")
suspend fun deleteNote(
@Path("id") id: Int
): Response<DeleteResponse>
}
network/ApiAdapter
object ApiAdapter {
private var API_SERVICE: ApiService? = null
private const val BASE_URL = "https://laravel.alumno.me/"
public var API_TOKEN: String = ""
@get:Synchronized
val instance: ApiService?
get() {
if (API_SERVICE == null) {
val client = OkHttpClient.Builder().addInterceptor(Interceptor { chain ->
val request = chain.request().newBuilder().addHeader("Authorization", "Bearer ${API_TOKEN}").build()
chain.proceed(request)
}).build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
API_SERVICE = retrofit.create(ApiService::class.java)
}
return API_SERVICE
}
}
network/ApiHelper
class ApiHelper(private val apiService: ApiService) {
suspend fun login(email: String, password: String) : LoginResponse {
return withContext(Dispatchers.IO) {
val response = apiService.login(email, password)
response.body() ?: LoginResponse(false, Login("", ""), "")
}
}
suspend fun register(name: String, email: String, password: String, confirmPassword: String) : LoginResponse {
return withContext(Dispatchers.IO) {
val response = apiService.register(name, email, password, confirmPassword)
response.body() ?: LoginResponse(false, Login("", ""), "")
}
}
suspend fun logout() : LogoutResponse {
return withContext(Dispatchers.IO) {
val response = apiService.logout()
response.body() ?: LogoutResponse(false, Logout(""), "")
}
}
suspend fun getNotes(): NotesResponse {
return withContext(Dispatchers.IO) {
val response = apiService.getNotes()
response.body() ?: NotesResponse(false, arrayListOf(), "")
}
}
suspend fun addNote(note: Note): NoteResponse {
return withContext(Dispatchers.IO) {
val response = apiService.addNote(note.title, note.description)
response.body() ?: NoteResponse(false, null, "")
}
}
suspend fun updateNote(note: Note): NoteResponse {
return withContext(Dispatchers.IO) {
val response = apiService.updateNote(note.id, note.title, note.description)
response.body() ?: NoteResponse(false, null, "")
}
}
suspend fun deleteNote(note: Note): DeleteResponse {
return withContext(Dispatchers.IO) {
val response = apiService.deleteNote(note.id)
response.body() ?: DeleteResponse(false, arrayListOf(), "")
}
}
}
repository/NoteRepository
class NoteRepository (private val apiHelper: ApiHelper) {
suspend fun login(email: String, password: String) =
apiHelper.login(email, password)
suspend fun register(name: String, email: String, password: String, confirmPassword: String) =
apiHelper.register(name, email, password, confirmPassword)
suspend fun logout() = apiHelper.logout()
suspend fun getNotes() = apiHelper.getNotes()
suspend fun addNote(note: Note) = apiHelper.addNote(note)
suspend fun updateNote(note: Note) = apiHelper.updateNote(note)
suspend fun deleteNote(note: Note) = apiHelper.deleteNote(note)
}
viewmodel/MainActivityViewModel
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
private val _notes: MutableLiveData<List<Note>> = MutableLiveData()
val notes: LiveData<List<Note>> get() = _notes
private var repository: NoteRepository = NoteRepository(ApiHelper(instance!!))
suspend fun getNotes(): NotesResponse {
val notesResponse = repository.getNotes()
_notes.value = notesResponse.notes
return notesResponse
}
suspend fun addNote(note: Note): Boolean {
val noteResponse = repository.addNote(note)
if (noteResponse.success) {
// Añadimos la nota a la lista, y reasignamos el valor de LiveData para que se actualice
//add note to _notes
_notes.value = _notes.value?.toMutableList()?.apply {
add(noteResponse.note!!)
}
}
return noteResponse.success
}
suspend fun updateNote(note: Note, newNote: Note): Boolean {
val noteResponse = repository.updateNote(newNote)
if (noteResponse.success) {
// Reasignamos el valor de LiveData para que se actualice
_notes.value = _notes.value.orEmpty().map {
if (it == note) newNote else it
}
_notes.value = _notes.value
}
return noteResponse.success
}
suspend fun deleteNote(note: Note): Boolean {
val noteResponse = repository.deleteNote(note)
if (noteResponse.success) {
// Reasignamos el valor de LiveData para que se actualice
_notes.value = _notes.value?.toMutableList()?.apply {
remove(note)
}
}
return noteResponse.success
}
suspend fun logout() : Boolean {
val logoutResponse = repository.logout()
return logoutResponse.success
}
}
LoginActivity
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private var repository: NoteRepository = NoteRepository(ApiHelper(ApiAdapter.instance!!))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
// Cargamos los datos de usuario
getLogin()
binding.btLogin.setOnClickListener {
toggleUI(false)
lifecycleScope.launch {
val email = binding.etEmail.text.toString()
val password = binding.etPassword.text.toString()
// Validamos que todos los datos del login estén correctos
if (validateLogin(email, password)) {
val response = repository.login(email, password)
if (response.success) {
withContext(Dispatchers.Main) {
toggleUI(true)
}
// Guardamos los datos del usuario
saveLogin(email, password)
// Lanzamos la actividad principal
val mainActivityIntent = Intent(this@LoginActivity, MainActivity::class.java)
mainActivityIntent.putExtra("API_TOKEN", response.login.token)
startActivity(mainActivityIntent)
finish()
} else {
withContext(Dispatchers.Main) {
val alertDialog: AlertDialog =
AlertDialog.Builder(this@LoginActivity).create().apply {
setTitle("Error")
setMessage("Error al iniciar sesión: " + response.message.ifEmpty { "No se ha podido iniciar sesión" })
setButton(
AlertDialog.BUTTON_POSITIVE,
"Aceptar"
) { dialog, _ ->
dialog.dismiss()
}
}
alertDialog.show()
toggleUI(true)
}
}
}
}
}
binding.btRegister.setOnClickListener {
startActivity(Intent(this@LoginActivity, RegisterActivity::class.java))
}
}
private fun toggleUI(state: Boolean) {
binding.btLogin.isEnabled = state
binding.btRegister.isEnabled = state
}
private suspend fun saveLogin(email: String, password: String) {
dataStore.edit { preferences ->
preferences[booleanPreferencesKey("rememberLogin")] = binding.cbRememberLogin.isChecked
if (binding.cbRememberLogin.isChecked) {
preferences[stringPreferencesKey("email")] = email
preferences[stringPreferencesKey("password")] = password
} else {
preferences.remove(stringPreferencesKey("email"))
preferences.remove(stringPreferencesKey("password"))
}
}
}
private fun getLogin() {
runBlocking {
dataStore.data.map { preferences ->
binding.cbRememberLogin.isChecked =
preferences[booleanPreferencesKey("rememberLogin")] ?: false
if (binding.cbRememberLogin.isChecked) {
binding.etEmail.setText(preferences[stringPreferencesKey("email")].orEmpty())
binding.etPassword.setText(preferences[stringPreferencesKey("password")].orEmpty())
}
}.first()
}
}
private fun validateLogin(email: String, password: String): Boolean {
var loginValidated = true
// Comprobamos si hay campos vacios
if (email.isEmpty() || password.isEmpty()) {
loginValidated = false
showMessage("No puedes dejar campos sin rellenar")
if (email.isEmpty())
binding.etEmail.error = "El email no puede estar vacio"
if (password.isEmpty())
binding.etPassword.error = "La contraseña no puede estar vacia"
toggleUI(true)
}
// Comprobamos si el email cumple el estandar en regex
if (loginValidated && !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
loginValidated = false
showMessage("El email no es válido")
binding.etEmail.error = "El email no es válido"
toggleUI(true)
}
return loginValidated
}
private fun showMessage(message: String) {
Toast.makeText(this@LoginActivity, message, Toast.LENGTH_SHORT).show()
}
}
RegisterActivity
class RegisterActivity: AppCompatActivity() {
private lateinit var binding: ActivityRegisterBinding
private var repository: NoteRepository = NoteRepository(ApiHelper(ApiAdapter.instance!!))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRegisterBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btRegistro.setOnClickListener {
binding.btRegistro.isEnabled = false
lifecycleScope.launch {
val name = binding.etTitle.text.toString()
val email = binding.etEmail.text.toString()
val password = binding.etPassword.text.toString()
val confirmPassword = binding.etConfirmPassword.text.toString()
// Validamos que todos los datos estén correctos
if (validateRegister(name, email, password, confirmPassword)) {
val response = repository.register(
name,
email,
password,
confirmPassword
)
if (response.success) {
withContext(Dispatchers.Main) {
binding.btRegistro.isEnabled = true
}
val mainActivityIntent = Intent(this@RegisterActivity, MainActivity::class.java)
mainActivityIntent.putExtra("API_TOKEN", response.login.token)
startActivity(mainActivityIntent)
finish()
} else {
withContext(Dispatchers.Main) {
val alertDialog: AlertDialog =
AlertDialog.Builder(this@RegisterActivity).create().apply {
setTitle("Error")
setMessage("Error al registrarse")
setButton(
AlertDialog.BUTTON_POSITIVE,
"Aceptar"
) { dialog, _ ->
dialog.dismiss()
}
}
alertDialog.show()
binding.btRegistro.isEnabled = true
}
}
}
}
}
}
private fun validateRegister(
name: String,
email: String,
password: String,
confirmationPassword: String
): Boolean {
var registerValidated = true
// Comprobamos que todos los campos estén rellenos
if (name.isEmpty() || email.isEmpty() || password.isEmpty() || confirmationPassword.isEmpty()) {
registerValidated = false
if (name.isEmpty()) binding.etTitle.error = "El nombre de usuario no puede estar vacio"
if (email.isEmpty()) binding.etEmail.error = "El email no puede estar vacio"
if (password.isEmpty()) binding.etPassword.error = "La contraseña no puede estar vacia"
if (confirmationPassword.isEmpty()) binding.etConfirmPassword.error =
"La confirmación de contraseña no puede estar vacia"
showMessage( "No se puede registrar. Hay campos sin rellenar")
binding.btRegistro.isEnabled = true
}
if (registerValidated && !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
registerValidated = false
showMessage("El email no es válido")
binding.etEmail.error = "El email no es válido"
binding.btRegistro.isEnabled = true
}
if (registerValidated && password != confirmationPassword) {
registerValidated = false
showMessage("Las contraseñas no coinciden")
binding.etConfirmPassword.error = "Las contraseñas no coinciden"
binding.btRegistro.isEnabled = true
}
return registerValidated
}
private fun showMessage(message: String) {
Toast.makeText(this@RegisterActivity, message, Toast.LENGTH_SHORT).show()
}
}
adapter/NoteAdapter
class NoteAdapter() : RecyclerView.Adapter<NoteAdapter.NoteViewHolder>() {
var notesList: List<Note> = emptyList()
var onItemClick: ((Note) -> Unit)? = null
var onLongItemClick: ((Note) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemBinding =
NoteItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NoteViewHolder(itemBinding)
}
override fun getItemCount(): Int = notesList.size
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val item = notesList[position]
holder.render(item)
}
fun getItem(position: Int): Note {
return notesList.get(position)
}
inner class NoteViewHolder(binding: NoteItemBinding) :
RecyclerView.ViewHolder(binding.root) {
val title = binding.tvTitle
val description = binding.tvDescription
init {
itemView.setOnClickListener {
onItemClick?.invoke(notesList[layoutPosition])
}
itemView.setOnLongClickListener {
onLongItemClick?.invoke(notesList[layoutPosition])
false
}
}
fun render(note: Note) {
title.text = note.title
description.text = note.description
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
private lateinit var adapter: NoteAdapter
private var backPressedOnce = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ApiAdapter.API_TOKEN = intent.getStringExtra("API_TOKEN").toString()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
setSupportActionBar(binding.toolbar)
initRecyclerView()
binding.fab.setOnClickListener {
addNote(this)
}
// Confirmación para salir de la actividad
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (backPressedOnce) {
logout()
} else {
backPressedOnce = true
Toast.makeText(this@MainActivity, "Presiona de nuevo para cerrar sesión", Toast.LENGTH_SHORT).show()
Handler(Looper.getMainLooper()).postDelayed(Runnable { backPressedOnce = false }, 2000)
}
}
})
}
private fun initRecyclerView() {
binding.recyclerNotes.layoutManager = LinearLayoutManager(this)
getNotes()
val itemSwipe = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
if (binding.recyclerNotes.isEnabled) {
deleteDialog(viewHolder)
} else {
adapter.notifyItemChanged(viewHolder.layoutPosition)
}
}
}
val swap = ItemTouchHelper(itemSwipe)
swap.attachToRecyclerView(binding.recyclerNotes)
}
private fun getNotes() {
lifecycleScope.launch {
val response = viewModel.getNotes()
if (response.success) {
adapter = NoteAdapter()
binding.recyclerNotes.adapter = adapter
adapter.onLongItemClick = {
editNote(it, this@MainActivity)
}
// Cuando los datos de LiveData cambian, reasignamos la lista, ordenada por id,
// falta usar DiffUtil para actualizar el RecyclerView en lugar de notifyDataSetChanged
viewModel.notes.observe(this@MainActivity) { notesList ->
adapter.notesList = notesList.sortedWith(compareBy({ it.id }))
adapter.notifyDataSetChanged()
}
} else {
withContext(Dispatchers.Main) {
showMessage("No se han podido cargar las notas")
}
}
}
}
private fun updateNotes() {
lifecycleScope.launch {
if (viewModel.getNotes().success) {
showMessage("Notas actualizadas")
} else {
showMessage("Error al actualizar las notas")
}
}
}
private fun addNote(context: Context) {
val dialogBinding: AddNoteBinding = AddNoteBinding.inflate(LayoutInflater.from(context))
val title = dialogBinding.etTitle
val description = dialogBinding.etTDescription
val addDialog = AlertDialog.Builder(context)
addDialog.setView(dialogBinding.root)
addDialog.setTitle("Añadir nota")
addDialog.setPositiveButton("Añadir") { dialog, _ ->
val titleNote = title.text.toString()
val descriptionNote = description.text.toString()
if (validateNote(titleNote, descriptionNote)) {
val note = Note(
0,
titleNote,
descriptionNote,
0,
)
lifecycleScope.launch {
if (viewModel.addNote(note)) {
showMessage("Se ha añadido la nota")
} else {
showMessage("No se ha podido añadir la nota")
}
}
}
}
addDialog.setNegativeButton("Cancelar") { dialog, _ ->
dialog.dismiss()
showMessage("Cancelado")
}
addDialog.create()
addDialog.show()
}
private fun editNote(note: Note, context: Context) {
val dialogBinding: AddNoteBinding = AddNoteBinding.inflate(LayoutInflater.from(context))
val titleNote = dialogBinding.etTitle
val descriptionNote = dialogBinding.etTDescription
titleNote.setText(note.title)
descriptionNote.setText(note.description)
val editDialog = AlertDialog.Builder(context)
editDialog.setView(dialogBinding.root)
editDialog.setTitle("Editar nota")
editDialog.setPositiveButton("Editar") { dialog, _ ->
val title = titleNote.text.toString()
val description = descriptionNote.text.toString()
if (validateNote(title, description)) {
val newNote = Note(note.id, title, description, note.user_id)
lifecycleScope.launch {
if (viewModel.updateNote(note, newNote)) {
showMessage("Se ha editado la nota")
dialog.dismiss()
} else
showMessage("No se ha podido editar la nota")
}
}
}
editDialog.setNegativeButton("Cancelar") { dialog, _ ->
dialog.dismiss()
showMessage("Cancelado")
}
editDialog.create()
editDialog.show()
}
private fun deleteDialog(viewHolder: RecyclerView.ViewHolder) {
val builder = AlertDialog.Builder(this)
builder.setTitle("Eliminar Nota")
builder.setMessage("¿Estás seguro de eliminar esta nota de la lista?")
builder.setPositiveButton("Aceptar") { _, _ ->
lifecycleScope.launch {
if (viewModel.deleteNote(adapter.getItem(viewHolder.layoutPosition))) {
showMessage("La nota se ha eliminado correctamente")
} else
showMessage("No se ha podido eliminar la nota")
}
}
builder.setNegativeButton("Cancelar") { _, _ ->
val position = viewHolder.adapterPosition
adapter.notifyItemChanged(position)
}
builder.show()
}
// Menu options
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_items, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_refresh -> {
updateNotes()
}
R.id.action_logout -> {
logout()
}
else -> {
super.onOptionsItemSelected(item)
}
}
return true
}
private fun logout() {
lifecycleScope.launch {
if (viewModel.logout()){
startActivity(Intent(this@MainActivity, LoginActivity::class.java))
finish()
} else
withContext(Dispatchers.Main) {
showMessage("Error al cerrar la sesión")
}
}
}
private fun validateNote(title: String, description: String) : Boolean {
var validated = true
if (title.isEmpty() || description.isEmpty() ) {
validated = false
showMessage("No se ha validado la nota. Hay campos sin rellenar")
}
return validated
}
private fun showMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Mejoras:
– Usar DiffUtil en el adapter
– Usar fragments (HomeFragment, AddFragment y EditFragment) y navegación entre ellos en la interface de usuario
Tarea presencial de la unidad 6 de HLC


Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.