update address

This commit is contained in:
shaulascr
2025-04-09 15:46:50 +07:00
parent 6803bbfdd8
commit cebefcf82b
14 changed files with 538 additions and 37 deletions

View File

@ -4,6 +4,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"

View File

@ -21,7 +21,7 @@ data class CreateAddressRequest (
@SerializedName("province_id")
val provId: Int,
@SerializedName("postal_code")
val postCode: String,
val postCode: String? = null,
@SerializedName("detail")
val detailAddress: String? = null,

View File

@ -0,0 +1,11 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class UpdateCart (
@SerializedName("cart_item_id")
val cartItemId: Int,
@SerializedName("quantity")
val quantity: Int
)

View File

@ -26,7 +26,7 @@ data class AddressesItem(
val provinceId: Int,
@field:SerializedName("phone")
val phone: Any,
val phone: String,
@field:SerializedName("street")
val street: String,
@ -35,7 +35,7 @@ data class AddressesItem(
val subdistrict: String,
@field:SerializedName("recipient")
val recipient: Any,
val recipient: String,
@field:SerializedName("id")
val id: Int,

View File

@ -7,6 +7,7 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
@ -14,6 +15,9 @@ import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.cart.ListCartResponse
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
import com.alya.ecommerce_serang.data.api.response.order.UpdateCartResponse
import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
@ -21,11 +25,13 @@ import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
interface ApiService {
@ -80,9 +86,8 @@ interface ApiService {
@POST("profile/addaddress")
suspend fun createAddress(
@Body addressRequest: CreateAddressRequest
): Response<AddressResponse>
@Body createAddressRequest: CreateAddressRequest
): Response<CreateAddressResponse>
@GET("mystore")
suspend fun getStore (): Response<StoreResponse>
@ -95,8 +100,23 @@ interface ApiService {
@Body cartRequest: CartItem
): Response<AddCartResponse>
@PUT("cart/update")
suspend fun updateCart(
@Body updateCart: UpdateCart
): Response<UpdateCartResponse>
@POST("couriercost")
suspend fun countCourierCost(
@Body courierCost : CourierCostRequest
): CourierCostResponse
): Response<CourierCostResponse>
@GET("cities/{id}")
suspend fun getCityProvId(
@Path("id") provId : Int
): Response<ListCityResponse>
@GET("provinces")
suspend fun getListProv(
): Response<ListProvinceResponse>
}

View File

@ -1,10 +1,14 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import retrofit2.Response
@ -33,17 +37,31 @@ class OrderRepository(private val apiService: ApiService) {
return if (response.isSuccessful) response.body() else null
}
//post data with message/response
suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result<CreateAddressResponse> {
return try {
val response = apiService.createAddress(createAddressRequest)
if (response.isSuccessful){
response.body()?.let {
Result.Success(it)
} ?: Result.Error(Exception("Add Address failed"))
} else {
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
}
} catch (e: Exception) {
Result.Error(e)
}
}
//not yet implement the api service address
// suspend fun getAddressDetails(addressId: Int): AddressesItem {
// // Simulate API call to get address details
// kotlinx.coroutines.delay(300) // Simulate network request
// // Return mock data
// return AddressesItem(
// id = addressId,
// label = "Rumah",
// fullAddress = "Jl. Pegangasan Timur No. 42, Jakarta"
// )
// }
suspend fun getListProvinces(): ListProvinceResponse? {
val response = apiService.getListProv()
return if (response.isSuccessful) response.body() else null
}
suspend fun getListCities(provId : Int): ListCityResponse?{
val response = apiService.getCityProvId(provId)
return if (response.isSuccessful) response.body() else null
}
}

View File

@ -10,12 +10,9 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
class UserRepository(private val apiService: ApiService) {
//post data without message/response
suspend fun requestOtpRep(email: String): OtpResponse {
// fun requestOtpRep(email: String): Result<String> {
return apiService.getOTP(OtpRequest(email))
}
suspend fun registerUser(request: RegisterRequest): String {

View File

@ -1,21 +1,289 @@
package com.alya.ecommerce_serang.ui.order
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import androidx.constraintlayout.motion.widget.Debug.getLocation
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch
class AddAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_add_address)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
private lateinit var binding: ActivityAddAddressBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private lateinit var profileUser: UserProfile
private lateinit var locationManager: LocationManager
private var latitude: Double? = null
private var longitude: Double? = null
private val provinceAdapter by lazy { ProvinceAdapter(this) }
private val cityAdapter by lazy { CityAdapter(this) }
private val viewModel: AddAddressViewModel by viewModels {
SavedStateViewModelFactory(this) { savedStateHandle ->
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
AddAddressViewModel(orderRepository, savedStateHandle)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
setupToolbar()
setupAutoComplete()
setupButtonListeners()
collectFlows()
requestLocationPermission()
}
private fun viewModelAddAddress(request: CreateAddressRequest) {
// Call the private fun in your ViewModel using reflection or expose it in ViewModel
val method = AddAddressViewModel::class.java.getDeclaredMethod("addAddress", CreateAddressRequest::class.java)
method.isAccessible = true
method.invoke(viewModel, request)
}
// UI setup methods
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener { finish() }
}
private fun setupAutoComplete() {
// Set adapters
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
// Set listeners
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
provinceAdapter.getProvinceId(position)?.let { provinceId ->
viewModel.getCities(provinceId)
binding.autoCompleteKabupaten.text.clear()
}
}
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
cityAdapter.getCityId(position)?.let { cityId ->
viewModel.selectedCityId = cityId
}
}
}
private fun setupButtonListeners() {
binding.buttonSimpan.setOnClickListener {
validateAndSubmitForm()
}
}
private fun collectFlows() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.provincesState.collect { state ->
handleProvinceState(state)
}
}
launch {
viewModel.citiesState.collect { state ->
handleCityState(state)
}
}
launch {
viewModel.addressSubmissionState.collect { state ->
handleAddressSubmissionState(state)
}
}
}
}
}
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
when (state) {
is ViewState.Loading -> null //showProvinceLoading(true)
is ViewState.Success -> {
provinceAdapter.updateData(state.data)
}
is ViewState.Error -> {
showError(state.message)
}
}
}
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
when (state) {
is ViewState.Loading -> null //showCityLoading(true)
is ViewState.Success -> {
// showCityLoading(false)
cityAdapter.updateData(state.data)
}
is ViewState.Error -> {
// showCityLoading(false)
showError(state.message)
}
}
}
private fun handleAddressSubmissionState(state: ViewState<String>) {
when (state) {
is ViewState.Loading -> showSubmitLoading(true)
is ViewState.Success -> {
showSubmitLoading(false)
showSuccessAndFinish(state.data)
}
is ViewState.Error -> {
showSubmitLoading(false)
showError(state.message)
}
}
}
// private fun showProvinceLoading(isLoading: Boolean) {
// // Implement province loading indicator
// binding.provinceProgressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE
// }
//
// private fun showCityLoading(isLoading: Boolean) {
// // Implement city loading indicator
// binding.cityProgressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE
// }
//
private fun showSubmitLoading(isLoading: Boolean) {
binding.buttonSimpan.isEnabled = !isLoading
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
// You might want to show a progress bar as well
}
private fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
private fun showSuccessAndFinish(message: String) {
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
finish()
}
private fun validateAndSubmitForm() {
val lat = latitude
val long = longitude
if (lat == null || long == null) {
showError("Lokasi belum terdeteksi")
return
}
val street = binding.etDetailAlamat.text.toString()
val subDistrict = binding.etKecamatan.text.toString()
val postalCode = binding.etKodePos.text.toString()
val recipient = binding.etNamaPenerima.text.toString()
val phone = binding.etNomorHp.text.toString()
val userId = profileUser.userId
val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId
val cityId = viewModel.selectedCityId
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
showError("Lengkapi semua field wajib")
return
}
if (provinceId == null) {
showError("Pilih provinsi terlebih dahulu")
return
}
if (cityId == null) {
showError("Pilih kota/kabupaten terlebih dahulu")
return
}
val request = CreateAddressRequest(
lat = lat,
long = long,
street = street,
subDistrict = subDistrict,
cityId = cityId,
provId = provinceId,
postCode = postalCode,
detailAddress = street,
userId = userId,
recipient = recipient,
phone = phone,
isStoreLocation = isStoreLocation
)
viewModel.addAddress(request)
}
private val locationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) getLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show()
}
private fun requestLocationPermission() {
locationPermissionLauncher.launch(android.Manifest.permission.ACCESS_FINE_LOCATION)
}
@SuppressLint("MissingPermission")
private fun requestLocation() {
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if (!isGpsEnabled && !isNetworkEnabled) {
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
return
}
val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
locationManager.requestSingleUpdate(provider, object : LocationListener {
override fun onLocationChanged(location: Location) {
latitude = location.latitude
longitude = location.longitude
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {
Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show()
}
}, null)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestLocation()
} else {
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -0,0 +1,105 @@
package com.alya.ecommerce_serang.ui.order
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
// Flow states for data
private val _addressSubmissionState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<String>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
val addressSubmissionState = _addressSubmissionState.asStateFlow()
private val _provincesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<ProvincesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
val provincesState = _provincesState.asStateFlow()
private val _citiesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<CitiesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
val citiesState = _citiesState.asStateFlow()
// Stored in SavedStateHandle for configuration changes
var selectedProvinceId: Int?
get() = savedStateHandle.get<Int>("selectedProvinceId")
set(value) { savedStateHandle["selectedProvinceId"] = value }
var selectedCityId: Int?
get() = savedStateHandle.get<Int>("selectedCityId")
set(value) { savedStateHandle["selectedCityId"] = value }
init {
// Load provinces on initialization
getProvinces()
}
fun addAddress(request: CreateAddressRequest){
viewModelScope.launch {
when (val result = repository.addAddress(request)) {
is Result.Success -> {
val message = result.data.message // Ambil `message` dari CreateAddressResponse
_addressSubmissionState.value = ViewState.Success(message)
}
is Result.Error -> {
_addressSubmissionState.value = ViewState.Error(result.exception.message ?: "Unknown error")
}
is Result.Loading -> {
// Optional, karena sudah set Loading di awal
}
}
}
}
fun getProvinces(){
viewModelScope.launch {
try {
val result = repository.getListProvinces()
result?.let {
_provincesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.provinces)
}
} catch (e: Exception) {
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
}
}
}
fun getCities(provinceId: Int){
viewModelScope.launch {
try {
selectedProvinceId = provinceId
val result = repository.getListCities(provinceId)
result?.let {
_citiesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.cities)
}
} catch (e: Exception) {
Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
}
}
}
fun setSelectedProvinceId(id: Int) {
selectedProvinceId = id
}
fun setSelectedCityId(id: Int) {
selectedCityId = id
}
fun getSelectedProvinceId(): Int? = selectedProvinceId
fun getSelectedCityId(): Int? = selectedCityId
companion object {
private const val TAG = "AddAddressViewModel"
}
}
sealed class ViewState<out T> {
object Loading : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
data class Success<T>(val data: T) : com.alya.ecommerce_serang.ui.order.ViewState<T>()
data class Error(val message: String) : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
}

View File

@ -0,0 +1,49 @@
package com.alya.ecommerce_serang.ui.order
import android.content.Context
import android.widget.ArrayAdapter
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
// UI adapters and helpers
class ProvinceAdapter(
context: Context,
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
private val provinces = ArrayList<ProvincesItem>()
fun updateData(newProvinces: List<ProvincesItem>) {
provinces.clear()
provinces.addAll(newProvinces)
clear()
addAll(provinces.map { it.province })
notifyDataSetChanged()
}
fun getProvinceId(position: Int): Int? {
return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
}
}
class CityAdapter(
context: Context,
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
private val cities = ArrayList<CitiesItem>()
fun updateData(newCities: List<CitiesItem>) {
cities.clear()
cities.addAll(newCities)
clear()
addAll(cities.map { it.cityName })
notifyDataSetChanged()
}
fun getCityId(position: Int): Int? {
return cities.getOrNull(position)?.cityId?.toIntOrNull()
}
}

View File

@ -1,7 +1,10 @@
package com.alya.ecommerce_serang.utils
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.savedstate.SavedStateRegistryOwner
class BaseViewModelFactory<VM : ViewModel>(
private val creator: () -> VM
@ -10,4 +13,20 @@ class BaseViewModelFactory<VM : ViewModel>(
@Suppress("UNCHECKED_CAST")
return creator() as T
}
}
// Add a new factory for SavedStateHandle ViewModels
class SavedStateViewModelFactory<VM : ViewModel>(
private val owner: SavedStateRegistryOwner,
private val creator: (SavedStateHandle) -> VM
) : AbstractSavedStateViewModelFactory(owner, null) {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return creator(handle) as T
}
}

View File

@ -131,7 +131,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Kabupaten / Kota"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
@ -139,6 +138,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:hint="Masukkan Kabupaten"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>

View File

@ -29,9 +29,11 @@
android:textAlignment="textEnd"
android:layout_gravity="center"
android:paddingEnd="16dp"
android:paddingVertical="16dp"
android:textColor="@color/blue_500"
android:fontFamily="@font/dmsans_semibold"
android:textSize="14sp"
android:clickable="true"
android:text="Tambah Alamat"
tools:ignore="RtlCompat" />
</LinearLayout>

View File

@ -47,13 +47,23 @@
android:layout_gravity="start"
android:padding="8dp">
<ImageView
android:id="@+id/iv_location_checkout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:tint="@color/blue_500"
android:layout_marginTop="16dp"
android:src="@drawable/baseline_location_pin_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout
android:id="@+id/linear_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_location_checkout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_change_address">