Building an Android App with Kotlin, Retrofit, and Coroutines: A Practical Guide
Introduction
In this article, I'll share my experience building a production-ready Android app for MyZubster, an open-source skill exchange platform. I'll cover the key architectural decisions and code patterns I used.
Tech Stack Overview
| Component | Technology |
|---|---|
| Language | Kotlin |
| Networking | Retrofit + OkHttp |
| Async | Coroutines + Flow |
| UI | Material Design + ViewBinding |
| Storage | SharedPreferences (Token) |
| QR Code | ZXing |
| Architecture | MVVM + Repository Pattern |
1. Authentication & Token Management
I implemented JWT-based authentication with automatic token injection.
kotlin
// TokenManager.kt
class TokenManager(context: Context) {
private val prefs = context.getSharedPreferences("MyZubsterPrefs", Context.MODE_PRIVATE)
fun saveAuthData(token: String, userId: String, email: String, name: String) {
prefs.edit().apply {
putString("jwt_token", token)
putString("user_id", userId)
putString("user_email", email)
putString("user_name", name)
apply()
}
}
fun getToken(): String? = prefs.getString("jwt_token", null)
fun isLoggedIn(): Boolean = !getToken().isNullOrEmpty()
}
2. Networking with Retrofit
I used Retrofit with an AuthInterceptor to automatically add the JWT token to requests.
kotlin
// AuthInterceptor.kt
class AuthInterceptor(private val tokenManager: TokenManager) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val token = tokenManager.getToken()
val newRequest = if (!token.isNullOrEmpty()) {
request.newBuilder()
.header("Authorization", "Bearer $token")
.build()
} else {
request
}
return chain.proceed(newRequest)
}
}
3. Booking History with Pagination
I implemented a RecyclerView with pagination using the Repository pattern.
kotlin
// BookingHistoryViewModel.kt
class BookingHistoryViewModel : ViewModel() {
private val _bookings = MutableStateFlow<List<BookingHistory>>(emptyList())
val bookings: StateFlow<List<BookingHistory>> = _bookings.asStateFlow()
private var currentPage = 1
private var hasMoreData = true
fun loadBookings(userId: String, page: Int = 1) {
viewModelScope.launch {
try {
val response = repository.getBookingHistory(userId, page, 10)
if (response.isSuccessful) {
_bookings.value = response.body()?.data ?: emptyList()
currentPage = response.body()?.pagination?.page ?: 1
}
} catch (e: Exception) {
// Handle error
}
}
}
}
4. QR Code Generation
For Monero payments, I integrated QR code generation for payment addresses.
kotlin
// PaymentActivity.kt
fun generateQRCode(address: String): Bitmap {
val writer = QRCodeWriter()
val bitMatrix = writer.encode(address, BarcodeFormat.QR_CODE, 200, 200)
val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.RGB_565)
for (x in 0 until 200) {
for (y in 0 until 200) {
bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE)
}
}
return bitmap
}
5. Session Management with Countdown
I implemented a session timer that shows remaining time in the UI.
kotlin
// SessionManager.kt
class SessionManager(private val context: Context) {
fun getSessionCountdown(callback: (String) -> Unit) {
Thread {
while (isSessionValid()) {
val remaining = tokenManager.getSessionRemainingTime()
val formatted = formatTime(remaining)
Handler(Looper.getMainLooper()).post {
callback(formatted)
}
sleep(1000)
}
}.start()
}
}
Key Takeaways
Use Repository Pattern - Separates data sources from UI logic
Implement AuthInterceptor - Automates token injection
Use StateFlow - For reactive UI updates
Handle Pagination - For efficient data loading
Session Management - Improves user experience
Resources
The full project is open-source and available on GitHub:
DanielIoni-creator/MyZubsterAPP
Follow me on GitHub: DanielIoni-creator
Top comments (0)