Aplicación Todo en Kotlin
App en Kotlin que se comunica con el API Rest creado en laravel.alumno.me
¿Esquema de la red?
Aplicación en Kotlin
ApiService
interface ApiService { //@POST("api/register") @FormUrlEncoded @POST("api/register") suspend fun register( @Field("name") name: String?, @Field("email") email: String?, @Field("password") password: String?, @Field("confirm_password") confirmPassword: String? ): Response<RegisterResponse?> //@POST("api/login") @FormUrlEncoded @POST("api/login") suspend fun login( @Field("email") email: String?, @Field("password") password: String? ): Response<LoginResponse?> }
ApiRestClient
object ApiRestClient { private var API_SERVICE: ApiService? = null const val BASE_URL = Configuration.BASE_URL @get:Synchronized val instance: ApiService? get() { if (API_SERVICE == null) { val okHttpBuilder: Builder = Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(5, TimeUnit.SECONDS) val gson = GsonBuilder() .setDateFormat("dd-MM-yyyy") .create() val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .client(okHttpBuilder.build()) .build() API_SERVICE = retrofit.create(ApiService::class.java) } return API_SERVICE } }
ApiTokenService
interface ApiTokenService { @POST("api/logout") //@Header("Authorization") String token suspend fun logout(): Response<LogoutResponse?> // @get:GET("api/tasks") //@Header("Authorization") String token //val tasks: Response<GetTasksResponse?> @GET("api/tasks") //@Header("Authorization") String token suspend fun getTasks(): Response<GetTasksResponse?> @POST("api/tasks") //@Header("Authorization") String token, suspend fun createTask( @Body task: Task? ): Response<AddResponse?> @PUT("api/tasks/{id}") //@Header("Authorization") String token, suspend fun updateTask( @Body task: Task?, @Path("id") id: Long ): Response<AddResponse?> @DELETE("api/tasks/{id}") //@Header("Authorization") String token, suspend fun deleteTask( @Path("id") id: Long? ): Response<DeleteResponse?> @POST("api/email") suspend fun sendEmail(@Body email: Email?): Response<EmailResponse?> }
ApiTokenRestClient
object ApiTokenRestClient { private var API_SERVICE: ApiTokenService? = null const val BASE_URL = Configuration.BASE_URL @Synchronized fun getInstance(token: String?): ApiTokenService? { if (API_SERVICE == null) { val interceptor = Interceptor { chain -> val newRequest = chain.request().newBuilder() .addHeader("Accept", "application/json") .addHeader("authorization", "Bearer $token") .build() chain.proceed(newRequest) } val okHttpBuilder: Builder = Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(5, TimeUnit.SECONDS) .addInterceptor(interceptor) val gson = GsonBuilder() .setDateFormat("dd-MM-yyyy") .create() val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .client(okHttpBuilder.build()) .build() API_SERVICE = retrofit.create(ApiTokenService::class.java) } return API_SERVICE } fun deleteInstance() { API_SERVICE = null } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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:id="@+id/linearLayout3" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="104dp" android:layout_marginTop="40dp" android:layout_marginEnd="100dp" android:layout_marginBottom="65dp" android:src="@drawable/tasks_image" app:layout_constraintBottom_toTopOf="@+id/email" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.611" app:layout_constraintStart_toStartOf="@+id/textViewUpdate" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="12dp" android:layout_marginEnd="7dp" android:text="Email" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintEnd_toStartOf="@+id/email" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/email" /> <EditText android:id="@+id/email" android:layout_width="315dp" android:layout_height="51dp" android:layout_marginEnd="11dp" android:layout_marginBottom="24dp" android:ems="10" android:inputType="textEmailAddress" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintBottom_toTopOf="@+id/password" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/textViewUpdate" app:layout_constraintTop_toBottomOf="@+id/imageView" android:text="paco.portada@protonmail.com" /> <TextView android:id="@+id/textViewUpdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:layout_marginBottom="12dp" android:text="Password" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintBottom_toBottomOf="@+id/password" app:layout_constraintEnd_toStartOf="@+id/password" app:layout_constraintStart_toStartOf="parent" /> <EditText android:id="@+id/password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="15dp" android:layout_marginBottom="94dp" android:ems="10" android:inputType="textPassword" android:text="123456" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintBottom_toTopOf="@+id/login" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/textViewUpdate" app:layout_constraintTop_toBottomOf="@+id/email" /> <Button android:id="@+id/login" android:layout_width="319dp" android:layout_height="57dp" android:layout_marginStart="51dp" android:layout_marginTop="48dp" android:layout_marginEnd="51dp" android:background="@color/design_default_color_primary_dark" android:padding="12dp" android:text="Login" android:textColor="@color/white" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/password" /> <Button android:id="@+id/register" android:layout_width="193dp" android:layout_height="53dp" android:layout_marginBottom="172dp" android:background="@color/colorAccent" android:padding="12dp" android:text="Register" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.541" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/login" app:layout_constraintVertical_bias="0.756" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
class MainActivity : AppCompatActivity(), View.OnClickListener { lateinit var preferences: SharedPreferencesManager private lateinit var progressDialog: ProgressDialog private lateinit var binding: ActivityMainBinding companion object { const val APP = "ToDo App" const val EMAIL = "email" const val PASSWORD = "password" const val TOKEN = "token" private const val TAG = "LoginActivity" private const val REQUEST_REGISTER = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //activity_main.xml -> ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater) val view: View = binding!!.root setContentView(view) preferences = SharedPreferencesManager(this) binding!!.email.setText(preferences!!.email) binding!!.password.setText(preferences!!.password) binding!!.login.setOnClickListener(this) binding!!.register.setOnClickListener(this) } override fun onClick(v: View) { hideSoftKeyboard() if (v === binding!!.login) { if (validate() == false) { showMessage("Error al validar los datos") } else { loginByServer() } } else if (v === binding!!.register) { // Start the Register activity val intent = Intent(this, RegisterActivity::class.java) startActivityForResult(intent, REQUEST_REGISTER) //finish(); //overridePendingTransition(R.anim.push_left_in, R.anim.push_left_out); } } private fun loginByServer() { progressDialog = ProgressDialog(this) progressDialog!!.isIndeterminate = true progressDialog!!.setMessage("Login ...") progressDialog!!.setCancelable(false) progressDialog!!.show() binding!!.login.isEnabled = false val email = binding!!.email.text.toString() val password = binding!!.password.text.toString() lifecycleScope.launch { try { val response = ApiRestClient.instance!!.login(email, password) launch(Dispatchers.Main) { progressDialog!!.dismiss() hideSoftKeyboard() binding!!.login.isEnabled = true if (response.isSuccessful) { val loginResponse = response.body() if (loginResponse!!.success!!) { Log.d("onResponse", "" + response.body().toString()) //showMessage(response.body().getToken()); //guardar token en shared preferences preferences.save( binding.email.text.toString(), binding.password.text.toString(), loginResponse!!.data!!.token ) startActivity(Intent(applicationContext, PanelActivity::class.java)) finish() } else { showMessage("Error in login: Email/Password incorrectos") showMessage(loginResponse!!.message) Log.d("Login error", loginResponse!!.message.toString()) } } else { val message = StringBuilder() message.append("Failure in login: ") if (response.body() != null) { val loginResponse = response.body() message.append("\n" + loginResponse!!.message); } if (response.errorBody() != null) try { message.append("\n" + response.errorBody()!!.toString()) } catch (e: IOException) { e.printStackTrace() } showMessage(message.toString()) } } } catch (e: Exception) { launch(Dispatchers.Main) { hideSoftKeyboard() progressDialog!!.dismiss() binding!!.login.isEnabled = true binding!!.register.isEnabled = true var message: String = "Failure in the communication\n" if (e != null) { Log.d("onFailure", e.message.toString()) message += e.message.toString() } showMessage(message) } } } } private fun showMessage(s: String?) { Toast.makeText(this, s, Toast.LENGTH_SHORT).show() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_REGISTER) { if (resultCode == RESULT_OK) { // TODO: Implement successful signup logic here // Por defecto se hace login automáticamente después del registro // Habría que validar el email antes de realizar login //Guardar token y lanzar Panel preferences!!.save( data!!.extras!!.getString("email"), data.extras!!.getString("password"), data.extras!!.getString("token") ) startActivity(Intent(this, PanelActivity::class.java)) finish() //binding.inputEmail.setText(data.getExtras().getString("email")); //binding.inputPassword.setText(data.getExtras().getString("password")); } else if (requestCode == RESULT_CANCELED) { //no hacer nada, volver al login showMessage("Registro cancelado") } } } fun validate(): Boolean { var valid = true val email = binding!!.email.text.toString() val password = binding!!.password.text.toString() if (email.isEmpty() || !Patterns.EMAIL_ADDRESS.matcher(email).matches()) { binding!!.email.error = "Enter a valid email address" requestFocus(binding!!.email) valid = false } else { binding!!.email.error = null } if (password.isEmpty()) { binding!!.password.error = "Password is empty" requestFocus(binding!!.password) valid = false } else { binding!!.password.error = null } return valid } private fun requestFocus(view: View) { if (view.requestFocus()) { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) } } fun hideSoftKeyboard() { if (currentFocus != null) { val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(currentFocus!!.windowToken, 0) } } }
activity_panel.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/ConstraintLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.circularreveal.coordinatorlayout.CircularRevealCoordinatorLayout android:id="@+id/coordinatorLayout" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/floatingActionButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="Add task" android:layout_gravity="bottom|right" android:layout_marginRight="30sp" android:layout_marginBottom="30sp" android:clickable="true" app:backgroundTint="@color/colorDefault" app:srcCompat="@drawable/pen_plus" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:backgroundTint="@color/design_default_color_primary" /> </com.google.android.material.circularreveal.coordinatorlayout.CircularRevealCoordinatorLayout> </androidx.constraintlayout.widget.ConstraintLayout>
PanelActivity
class PanelActivity : AppCompatActivity(), View.OnClickListener { var positionClicked = 0 lateinit var progressDialog: ProgressDialog companion object { const val ADD_CODE = 100 const val UPDATE_CODE = 200 const val OK = 1 } //ApiService apiService; lateinit var preferences: SharedPreferencesManager private lateinit var binding: ActivityPanelBinding private lateinit var adapter: TodoAdapter // private val listTasks = mutableListOf<Task>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //activity_main.xml -> ActivityMainBinding binding = ActivityPanelBinding.inflate(layoutInflater) val view: View = binding!!.root setContentView(view) binding!!.floatingActionButton.setOnClickListener(this) preferences = SharedPreferencesManager(this) //showMessage("panel: " + preferences.getToken()); initRecyclerView(); //Destruir la instancia de Retrofit para que se cree una con el nuevo token ApiTokenRestClient.deleteInstance() downloadTasks() } override fun onCreateOptionsMenu(menu: Menu): Boolean { val menuInflater = menuInflater menuInflater.inflate(R.menu.menu_toolbar, menu) // super.onCreateOptionsMenu(menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.refresh -> //petición al servidor para descargar de nuevo los sitios downloadTasks() R.id.email -> { //send an email val i = Intent(this, EmailActivity::class.java) startActivity(i) } R.id.exit -> { //petición al servidor para anular el token (a la ruta /api/logout) logout() preferences!!.saveToken(null, null) startActivity(Intent(applicationContext, MainActivity::class.java)) finish() } } return true } private fun initRecyclerView() { adapter = TodoAdapter() // binding.rvDogs.setHasFixedSize(true) binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.adapter = adapter binding!!.recyclerView.addOnItemTouchListener( RecyclerTouchListener( this, binding!!.recyclerView, object : ClickListener { override fun onClick(view: View?, position: Int) { showMessage("Single Click on task with id: " + adapter!!.getAt(position).id) modify(adapter!!.getAt(position)) positionClicked = position } override fun onLongClick(view: View?, position: Int) { showMessage("Long press on position :$position") } }) ) val itemSwipe = object : SimpleCallback(0, 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.recyclerView.isEnabled) { val position = viewHolder.adapterPosition val id = adapter?.getId(position) confirmDialog(id, position) } else { binding.recyclerView.adapter?.notifyItemChanged(viewHolder.position) } } } val swap = ItemTouchHelper(itemSwipe) swap.attachToRecyclerView(binding.recyclerView) } override fun onClick(v: View) { if (v === binding!!.floatingActionButton) { val i = Intent(this, AddActivity::class.java) startActivityForResult(i, ADD_CODE) } } private fun downloadTasks() { progressDialog = ProgressDialog(this) progressDialog!!.setProgressStyle(ProgressDialog.STYLE_SPINNER) progressDialog!!.setMessage("Connecting . . .") progressDialog!!.setCancelable(false) progressDialog!!.show() lifecycleScope.launch { try { val response = ApiTokenRestClient.getInstance(preferences!!.token)!!.getTasks() launch(Dispatchers.Main) { progressDialog!!.dismiss() if (response.isSuccessful) { val getTasksResponse = response.body() if (getTasksResponse!!.success!!) { adapter!!.setTasks(getTasksResponse.data!!) showMessage("Tasks downloaded ok") } else { showMessage("Error downloading the tasks: " + getTasksResponse.message) } } else { val message = StringBuilder() message.append("Error downloading the tasks: ") if (response.body() != null) message.append("\n" + response.body()!!.toString()); if (response.errorBody() != null) try { message.append("\n" + response.errorBody()!!.toString()) } catch (e: IOException) { e.printStackTrace() } showMessage(message.toString()) } } } catch (e: Exception) { launch(Dispatchers.Main) { // hideSoftKeyboard() // progressDialog!!.dismiss() var message: String = "Failure in the communication\n" if (e != null) { Log.d("onFailure", e.message.toString()) message += e.message.toString() } showMessage(message) } } } } private fun showMessage(s: String?) { Toast.makeText(this, s, Toast.LENGTH_SHORT).show() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) val task = Task() if (requestCode == ADD_CODE) if (resultCode == OK) { task.id = data!!.getIntExtra("id", 1).toLong() task.description = data.getStringExtra("description") task.createdAt = data.getStringExtra("createdAt") adapter!!.add(task) } if (requestCode == UPDATE_CODE) if (resultCode == OK) { task.id = data!!.getIntExtra("id", 1).toLong() task.description = data.getStringExtra("description") task.createdAt = data.getStringExtra("createdAt") adapter!!.modifyAt(task, positionClicked) } } private fun modify(task: Task) { var i = Intent(this, UpdateActivity::class.java) i.putExtra("task", task) startActivityForResult(i, UPDATE_CODE) } // private fun confirmDialog(idTask: Int, description: String, position: Int) { private fun confirmDialog(id: Long?, position: Int) { val builder = AlertDialog.Builder(this) val description = adapter.getAt(position).description builder.setMessage("$description\n Do you want to delete?") .setTitle("Delete") .setPositiveButton("Confirm") { dialog, which -> connection(id, position) dialog.dismiss() } .setNegativeButton("Cancel") { dialog, which -> binding.recyclerView.adapter?.notifyItemChanged(position) } builder.show() } private fun connection(id: Long?, position: Int) { progressDialog!!.setProgressStyle(ProgressDialog.STYLE_SPINNER) progressDialog!!.setMessage("Connecting . . .") progressDialog!!.setCancelable(false) progressDialog!!.show() lifecycleScope.launch { try { val response = ApiTokenRestClient.getInstance(preferences!!.token)!!.deleteTask(id) launch(Dispatchers.Main) { // hideSoftKeyboard() progressDialog!!.dismiss() if (response.isSuccessful) { val deleteResponse = response.body() if (deleteResponse!!.success!!) { adapter!!.removeAt(position) showMessage("Task deleted OK") } else showMessage("Error deleting the task") } else { val message = StringBuilder() message.append("Error downloading the tasks: ") if (response.body() != null) message.append("\n" + response.body()!!.toString()); if (response.errorBody() != null) try { message.append("\n" + response.errorBody()!!.toString()) } catch (e: IOException) { e.printStackTrace() } showMessage(message.toString()) } } } catch (e: Exception) { launch(Dispatchers.Main) { // hideSoftKeyboard() progressDialog!!.dismiss() var message: String = "Failure in the communication\n" if (e != null) { Log.d("onFailure", e.message.toString()) message += e.message.toString() } showMessage(message) } } } } private fun logout() { lifecycleScope.launch { try { val response = ApiTokenRestClient.getInstance(preferences!!.token)!!.logout() launch(Dispatchers.Main) { progressDialog!!.dismiss() if (response.isSuccessful) { val logoutResponse = response.body() if (logoutResponse!!.success!!) { showMessage("Logout OK") } else showMessage("Error in logout") } else { val message = StringBuilder() message.append("Error in logout: ") if (response.body() != null) message.append("\n" + response.body()!!.toString()); if (response.errorBody() != null) try { message.append("\n" + response.errorBody()!!.toString()) } catch (e: IOException) { e.printStackTrace() } showMessage(message.toString()) } } } catch (e: Exception) { launch(Dispatchers.Main) { // hideSoftKeyboard() // progressDialog!!.dismiss() var message: String = "Failure in the communication\n" if (e != null) { Log.d("onFailure", e.message.toString()) message += e.message.toString() } showMessage(message) } } } } }
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.