auth/docs/KOTLIN_SIGNUP_FIX.md

236 lines
7.1 KiB
Markdown

# Kotlin Signup API Response Fix
## Problem
The error "Expected response body of the type 'class com.example.livingai...'" indicates that your Kotlin data class doesn't match the actual API response structure.
## Actual API Response Structure
The `/auth/signup` endpoint returns:
```json
{
"success": true,
"user": {
"id": "uuid-here",
"phone_number": "+919876543210",
"name": "John Doe",
"country_code": "+91",
"created_at": "2024-01-15T10:30:00Z"
},
"access_token": "jwt-token",
"refresh_token": "jwt-token",
"needs_profile": true,
"is_new_account": true,
"is_new_device": true,
"active_devices_count": 1,
"location_id": "uuid-or-null"
}
```
## Correct Kotlin Data Classes
### Option 1: Using Gson/Moshi with @SerializedName
```kotlin
import com.google.gson.annotations.SerializedName
// OR for Moshi: import com.squareup.moshi.Json
data class SignupUser(
val id: String,
@SerializedName("phone_number") val phoneNumber: String,
val name: String?,
@SerializedName("country_code") val countryCode: String?,
@SerializedName("created_at") val createdAt: String?
)
data class SignupResponse(
val success: Boolean,
val user: SignupUser,
@SerializedName("access_token") val accessToken: String,
@SerializedName("refresh_token") val refreshToken: String,
@SerializedName("needs_profile") val needsProfile: Boolean,
@SerializedName("is_new_account") val isNewAccount: Boolean,
@SerializedName("is_new_device") val isNewDevice: Boolean,
@SerializedName("active_devices_count") val activeDevicesCount: Int,
@SerializedName("location_id") val locationId: String?
)
```
### Option 2: Using Kotlinx Serialization
```kotlin
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class SignupUser(
val id: String,
@SerialName("phone_number") val phoneNumber: String,
val name: String? = null,
@SerialName("country_code") val countryCode: String? = null,
@SerialName("created_at") val createdAt: String? = null
)
@Serializable
data class SignupResponse(
val success: Boolean,
val user: SignupUser,
@SerialName("access_token") val accessToken: String,
@SerialName("refresh_token") val refreshToken: String,
@SerialName("needs_profile") val needsProfile: Boolean,
@SerialName("is_new_account") val isNewAccount: Boolean,
@SerialName("is_new_device") val isNewDevice: Boolean,
@SerialName("active_devices_count") val activeDevicesCount: Int,
@SerialName("location_id") val locationId: String? = null
)
```
### Option 3: Using Retrofit with Field Naming Strategy
If using Retrofit, you can configure it to handle snake_case automatically:
```kotlin
// In your Retrofit builder
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
)
)
.build()
// Then your data classes can use camelCase
data class SignupUser(
val id: String,
val phoneNumber: String, // Will map to "phone_number"
val name: String?,
val countryCode: String?, // Will map to "country_code"
val createdAt: String? // Will map to "created_at"
)
data class SignupResponse(
val success: Boolean,
val user: SignupUser,
val accessToken: String, // Will map to "access_token"
val refreshToken: String, // Will map to "refresh_token"
val needsProfile: Boolean,
val isNewAccount: Boolean,
val isNewDevice: Boolean,
val activeDevicesCount: Int,
val locationId: String?
)
```
## Complete Example with Error Handling
```kotlin
// SignupRequest.kt
data class SignupRequest(
val name: String,
@SerializedName("phone_number") val phoneNumber: String,
val state: String? = null,
val district: String? = null,
@SerializedName("city_village") val cityVillage: String? = null,
@SerializedName("device_id") val deviceId: String? = null,
@SerializedName("device_info") val deviceInfo: Map<String, String?>? = null
)
// SignupResponse.kt
data class SignupUser(
val id: String,
@SerializedName("phone_number") val phoneNumber: String,
val name: String?,
@SerializedName("country_code") val countryCode: String?,
@SerializedName("created_at") val createdAt: String?
)
data class SignupResponse(
val success: Boolean,
val user: SignupUser,
@SerializedName("access_token") val accessToken: String,
@SerializedName("refresh_token") val refreshToken: String,
@SerializedName("needs_profile") val needsProfile: Boolean,
@SerializedName("is_new_account") val isNewAccount: Boolean,
@SerializedName("is_new_device") val isNewDevice: Boolean,
@SerializedName("active_devices_count") val activeDevicesCount: Int,
@SerializedName("location_id") val locationId: String?
)
// ErrorResponse.kt
data class ErrorResponse(
val success: Boolean? = null,
val error: String? = null,
val message: String? = null,
@SerializedName("user_exists") val userExists: Boolean? = null
)
// Usage in your ViewModel or Repository
suspend fun signup(
name: String,
phoneNumber: String,
state: String? = null,
district: String? = null,
cityVillage: String? = null
): Result<SignupResponse> {
return try {
val request = SignupRequest(
name = name,
phoneNumber = phoneNumber,
state = state,
district = district,
cityVillage = cityVillage,
deviceId = getDeviceId(),
deviceInfo = getDeviceInfo()
)
val response = apiClient.post<SignupResponse>("/auth/signup", request)
if (response.isSuccessful && response.body()?.success == true) {
Result.success(response.body()!!)
} else {
// Parse error response
val errorBody = response.errorBody()?.string()
val errorResponse = Gson().fromJson(errorBody, ErrorResponse::class.java)
Result.failure(Exception(errorResponse.message ?: errorResponse.error ?: "Signup failed"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
```
## Common Issues and Fixes
### Issue 1: Field Name Mismatch
**Problem**: API uses `snake_case` but Kotlin uses `camelCase`
**Fix**: Use `@SerializedName` annotation or configure Retrofit with field naming policy
### Issue 2: Nullable Fields
**Problem**: Some fields might be null (like `location_id`)
**Fix**: Mark optional fields as nullable: `val locationId: String?`
### Issue 3: Nested Objects
**Problem**: `user` is a nested object
**Fix**: Create separate data class for `SignupUser`
### Issue 4: Type Mismatch
**Problem**: API returns `"success": true` but expecting different type
**Fix**: Use `Boolean` type: `val success: Boolean`
## Testing the Response
Add logging to see the actual response:
```kotlin
val response = apiClient.post("/auth/signup", request)
Log.d("Signup", "Response code: ${response.code()}")
Log.d("Signup", "Response body: ${response.body()?.string()}")
```
This will help you see exactly what the API is returning and adjust your data class accordingly.