Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id "com.google.devtools.ksp"
id 'com.google.dagger.hilt.android'
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}

android {
namespace 'ru.otus.basicarchitecture'
compileSdk 35
compileSdk 36

defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
targetSdk 35
targetSdk 36
versionCode 1
versionName "1.0"

buildConfigField "String", "dadata_api_key", "\"666c54c11310bd246fa5fb41224b9e4e74df886b\""

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand All @@ -30,14 +35,46 @@ android {
kotlinOptions {
jvmTarget = '17'
}
buildFeatures{
viewBinding = true
buildConfig = true
}
ksp {
arg("dagger.hilt.disableModulesHaveInstallInCheck", "true")
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.15.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.core:core-ktx:1.17.0'
implementation 'androidx.activity:activity-ktx:1.12.0'
implementation 'androidx.fragment:fragment-ktx:1.8.8'
implementation("androidx.navigation:navigation-fragment-ktx:2.9.0" )
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.13.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.6'

implementation("com.google.dagger:hilt-android:2.57.2")
ksp "com.google.dagger:hilt-compiler:2.57.2"

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("com.google.code.gson:gson:2.8.5")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

implementation 'com.squareup.okhttp3:okhttp:4.12.0'

testImplementation 'androidx.arch.core:core-testing:2.2.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0'
testImplementation 'org.mockito:mockito-core:5.12.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'
testImplementation 'org.mockito:mockito-inline:5.2.0'
testImplementation 'net.bytebuddy:byte-buddy:1.14.15'
androidTestImplementation 'org.mockito:mockito-android:5.12.0'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<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:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/AddressApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.otus.basicarchitecture

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.Headers
import retrofit2.http.POST

interface AddressApiService {
@Headers("Content-Type: application/json")
@POST("suggest/address")
suspend fun suggestAddress(
@Header("Authorization") token: String,
@Body request: AddressRequestDto,
): Response<AddressResponseDto>
}
10 changes: 10 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/AddressDataDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.otus.basicarchitecture

data class AddressDataDto(
val country: String?,
val city: String?,
val street: String?,
val house: String?,
val block: String?,
val fullAddress: String? = null
)
12 changes: 12 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/AddressMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.otus.basicarchitecture

class AddressMapper {
fun mapDtoToEntity(dto: AddressDataDto) = UserAddress(
fullAddress = dto.fullAddress ?: "",
country = dto.country ?: "",
city = dto.city ?: "",
street = dto.street ?: "",
house = dto.house ?: "",
block = dto.block ?: ""
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.basicarchitecture

interface AddressRepository {
suspend fun suggestAddress(query: String): List<UserAddress>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ru.otus.basicarchitecture

import android.util.Log
import java.io.IOException
import javax.inject.Inject

class AddressRepositoryImpl @Inject constructor(
private val addressApiService: AddressApiService
) : AddressRepository {
override suspend fun suggestAddress(query: String): List<UserAddress> {
val token = "Token ${BuildConfig.dadata_api_key}"

val response = addressApiService.suggestAddress(token, AddressRequestDto(query))

if (!response.isSuccessful){
val errorResponse = response.errorBody()?.string()
Log.e("API Error", "Error response: $errorResponse")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше еще пробрасывать исключение при неуспешном ответе, чтобы ВМ могла корректно обработать ошибку и показать пользователю сообщение.

throw IOException("Error response: $errorResponse")
}

val listAddressDataDto = response.body()?.suggestions?.map { suggestion ->
suggestion.data.copy(fullAddress = suggestion.unrestricted_value)
}

val mapper = AddressMapper()
val listUserAddress = listAddressDataDto?.map {
mapper.mapDtoToEntity(it)
}

return listUserAddress ?: listOf()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.basicarchitecture

data class AddressRequestDto(
val query: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.basicarchitecture

data class AddressResponseDto(
val suggestions: List<AddressSuggestionDto>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.otus.basicarchitecture


class AddressSuggestUseCase(private val addressRepository: AddressRepository) {
suspend operator fun invoke(query: String): List<UserAddress> {
return addressRepository.suggestAddress(query)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture

data class AddressSuggestionDto(
val value: String,
val unrestricted_value: String,
val data: AddressDataDto
)
70 changes: 70 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/AddressVewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package ru.otus.basicarchitecture


import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class AddressViewModel @Inject constructor(
private val wizardCache: WizardCache,
private val addressSuggestUseCase: AddressSuggestUseCase
): ViewModel() {
private var _listUserAddress = MutableLiveData<List<UserAddress>>()
val listUserAddress: LiveData<List<UserAddress>>
get() = _listUserAddress
private var _canContinue = MutableLiveData<Boolean>(false)
val canContinue: LiveData<Boolean>
get() = _canContinue
private var _errorNetwork = MutableLiveData<Boolean>()
val errorNetwork: LiveData<Boolean>
get() = _errorNetwork
private var _errorEmptyAddress = MutableLiveData<Boolean>()
val errorEmptyAddress: LiveData<Boolean>
get() = _errorEmptyAddress

private var current_job: Job? = null

fun validateData() {
val successful = checkEmptyFields()

if (successful == false){
_canContinue.value = false
return
}
_canContinue.value = true
}

fun setAddress(fullAddress: String) {
wizardCache.userAddress.fullAddress = fullAddress
}

fun searchAddress(query: String) {
current_job?.cancel()

current_job = viewModelScope.launch {
try {
val result = addressSuggestUseCase.invoke(query)
_listUserAddress.postValue(result)
} catch (e: Exception) {
_errorNetwork.postValue(true)
}
}
}

private fun checkEmptyFields(): Boolean{
var successful = true
if (wizardCache.userAddress.fullAddress.isBlank()){
_errorEmptyAddress.value = true
successful = false
} else{
_errorEmptyAddress.value = false
}
return successful
}
}
Loading