🧭 OBJETIVO GENERAL
Agregar un módulo “Calendario sincronizado” que:
- Muestre un calendario (
CalendarView)
- Liste los eventos del día (RecyclerView)
- Se autentique con Google (Sign-In)
- Permita leer o agregar eventos de Google Calendar
🧩 1️⃣ ESTRUCTURA DE CARPETAS Y ARCHIVOS
📁 com.example.api_calendar
├─ model/
│ ├─ AcademicEvent.kt → Modelo base de eventos
│
├─ ui/
│ ├─ CalendarActivity.kt → Control principal del calendario
│ ├─ GoogleSignInActivity.kt → Maneja autenticación con Google
│ ├─ adapters/
│ │ └─ EventAdapter.kt → Adaptador para mostrar eventos del día
│ └─ google/
│ ├─ GoogleCalendarService.kt → Clase helper para llamadas API REST
│ └─ GoogleAuthHelper.kt → Helper para autenticación y tokens
│
└─ res/
├─ layout/
│ ├─ activity_calendar_google.xml
│ ├─ item_event.xml
│ └─ activity_google_sign_in.xml
├─ values/
│ ├─ strings.xml
│ ├─ colors.xml
│ └─ themes.xml
└─ drawable/
├─ ic_calendar_month_24.xml
├─ ic_event_24.xml
└─ ic_google_24.xml
⚙️ 2️⃣ CONFIGURACIÓN DE DEPENDENCIAS
Abre tu archivo build.gradle.kts (:app) y agrega las siguientes líneas:
dependencies {
// Google Sign-In
implementation("com.google.android.gms:play-services-auth:21.2.0")
// HTTP Client (para llamadas a la API de Google Calendar)
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.moshi:moshi:1.15.1") // JSON parser opcional
}
🔁 Luego haz Sync Project with Gradle Files.
🔐 3️⃣ AUTENTICACIÓN CON GOOGLE
🧱 GoogleSignInActivity.kt
package com.example.api_calendar.ui
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.api_calendar.databinding.ActivityGoogleSignInBinding
import com.google.android.gms.auth.api.signin.*
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
class GoogleSignInActivity : AppCompatActivity() {
private lateinit var binding: ActivityGoogleSignInBinding
private lateinit var googleSignInClient: GoogleSignInClient
private val RC_SIGN_IN = 1001
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityGoogleSignInBinding.inflate(layoutInflater)
setContentView(binding.root)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestScopes(Scope("https://www.googleapis.com/auth/calendar"))
.requestServerAuthCode("YOUR_CLIENT_ID.apps.googleusercontent.com") // Obtén de Google Cloud Console
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
binding.btnGoogleSignIn.setOnClickListener {
startActivityForResult(googleSignInClient.signInIntent, RC_SIGN_IN)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
// 🔹 Redirigir a CalendarActivity una vez logueado
val intent = Intent(this, CalendarActivity::class.java)
intent.putExtra("ACCOUNT_EMAIL", account?.email)
startActivity(intent)
finish()
} catch (e: ApiException) {
e.printStackTrace()
}
}
}
}
🗓️ 4️⃣ INTERFAZ PRINCIPAL DEL CALENDARIO
📄 activity_calendar_google.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:background="@color/md_black">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:title="@string/menu_calendar"
android:background="@color/md_surface"
android:titleTextColor="@color/md_on"/>
<CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvEventsDay"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="8dp"/>
</LinearLayout>
🧱 CalendarActivity.kt
package com.example.api_calendar.ui
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.api_calendar.databinding.ActivityCalendarGoogleBinding
import com.example.api_calendar.model.AcademicEvent
import com.example.api_calendar.ui.adapters.EventAdapter
import com.example.api_calendar.ui.google.GoogleCalendarService
import kotlinx.coroutines.*
class CalendarActivity : AppCompatActivity() {
private lateinit var binding: ActivityCalendarGoogleBinding
private val googleService = GoogleCalendarService()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCalendarGoogleBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
binding.rvEventsDay.layoutManager = LinearLayoutManager(this)
binding.calendarView.setOnDateChangeListener { _, year, month, dayOfMonth ->
val date = "$year-${month + 1}-$dayOfMonth"
loadEventsFromGoogle(date)
}
}
private fun loadEventsFromGoogle(date: String) {
CoroutineScope(Dispatchers.IO).launch {
val events = googleService.fetchEvents(date)
withContext(Dispatchers.Main) {
binding.rvEventsDay.adapter = EventAdapter(events)
}
}
}
}
🌐 5️⃣ SERVICIO DE GOOGLE CALENDAR API
📄 GoogleCalendarService.kt
package com.example.api_calendar.ui.google
import com.example.api_calendar.model.AcademicEvent
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
class GoogleCalendarService {
private val http = OkHttpClient()
suspend fun fetchEvents(date: String): List<AcademicEvent> {
val events = mutableListOf<AcademicEvent>()
// Simulación de token (deberás obtenerlo desde GoogleAuthHelper)
val token = "ACCESS_TOKEN"
val url = "https://www.googleapis.com/calendar/v3/calendars/primary/events?" +
"timeMin=${date}T00:00:00Z&timeMax=${date}T23:59:59Z&singleEvents=true&orderBy=startTime"
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "Bearer $token")
.build()
val response = http.newCall(request).execute()
if (response.isSuccessful) {
val json = JSONObject(response.body?.string() ?: "")
val items = json.getJSONArray("items")
for (i in 0 until items.length()) {
val event = items.getJSONObject(i)
val title = event.getString("summary")
val start = event.getJSONObject("start").optString("dateTime", "—")
events.add(AcademicEvent(title, start, ""))
}
}
return events
}
}
🧠 6️⃣ MODELO DE EVENTO
📄 model/AcademicEvent.kt
package com.example.api_calendar.model
data class AcademicEvent(
val title: String,
val time: String,
val detail: String
)
🧩 7️⃣ ADAPTADOR PARA EVENTOS
📄 ui/adapters/EventAdapter.kt
package com.example.api_calendar.ui.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.api_calendar.databinding.ItemEventBinding
import com.example.api_calendar.model.AcademicEvent
class EventAdapter(private val items: List<AcademicEvent>) :
RecyclerView.Adapter<EventAdapter.VH>() {
inner class VH(val vb: ItemEventBinding) : RecyclerView.ViewHolder(vb.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val vb = ItemEventBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return VH(vb)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val e = items[position]
holder.vb.tvTitle.text = e.title
holder.vb.tvWhen.text = e.time
holder.vb.tvDetail.text = e.detail
}
override fun getItemCount() = items.size
}
🗂️ 8️⃣ LAYOUT DE ITEM
📄 item_event.xml
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardBackgroundColor="@color/md_card"
app:cardCornerRadius="16dp">
<LinearLayout
android:orientation="vertical"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvTitle"
android:textColor="@color/md_on"
android:textStyle="bold"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tvWhen"
android:textColor="@color/md_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tvDetail"
android:textColor="@color/md_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
✅ RESULTADO FINAL
Con esta estructura tendrás:
✅ Autenticación de usuario con Google
✅ CalendarView para seleccionar fechas
✅ Llamadas a Google Calendar API
✅ Listado de eventos del día con diseño Material
✅ Base lista para sincronización bidireccional
🧭 OBJETIVO GENERAL
Agregar un módulo “Calendario sincronizado” que:
CalendarView)🧩 1️⃣ ESTRUCTURA DE CARPETAS Y ARCHIVOS
📁
com.example.api_calendar⚙️ 2️⃣ CONFIGURACIÓN DE DEPENDENCIAS
Abre tu archivo
build.gradle.kts (:app)y agrega las siguientes líneas:dependencies { // Google Sign-In implementation("com.google.android.gms:play-services-auth:21.2.0") // HTTP Client (para llamadas a la API de Google Calendar) implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.moshi:moshi:1.15.1") // JSON parser opcional }🔁 Luego haz Sync Project with Gradle Files.
🔐 3️⃣ AUTENTICACIÓN CON GOOGLE
🧱
GoogleSignInActivity.kt🗓️ 4️⃣ INTERFAZ PRINCIPAL DEL CALENDARIO
📄
activity_calendar_google.xml🧱
CalendarActivity.kt🌐 5️⃣ SERVICIO DE GOOGLE CALENDAR API
📄
GoogleCalendarService.kt🧠 6️⃣ MODELO DE EVENTO
📄
model/AcademicEvent.kt🧩 7️⃣ ADAPTADOR PARA EVENTOS
📄
ui/adapters/EventAdapter.kt🗂️ 8️⃣ LAYOUT DE ITEM
📄
item_event.xml✅ RESULTADO FINAL
Con esta estructura tendrás:
✅ Autenticación de usuario con Google
✅ CalendarView para seleccionar fechas
✅ Llamadas a Google Calendar API
✅ Listado de eventos del día con diseño Material
✅ Base lista para sincronización bidireccional