Android Kotlin Fundamentals Course 코드랩 하면서 노트. Android Kotlin Fundamentals
Add "for sale" images to the overview
// MarsProperty.kt
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double
) {
val isRental
get() = type == "rent"
}
Update grid_view_item.xml
.
<layout ...>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:padding="2dp"
android:scaleType="centerCrop"
app:imageUrl="@{property.imgSrcUrl}"
tools:src="@tools:sample/backgrounds/scenic" />
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
tools:src="@drawable/ic_for_sale_outline" />
</FrameLayout>
<data>
<import type="android.view.View" />
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data>
</layout>
Filter the results
The URL will be:
// https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy
Update the Mars API service.
// MarsApiService.kt
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all")
}
// ...
interface MarsApiService {
@GET("realestate")
suspend fun getProperties(@Query("filter") type: String):
List<MarsProperty>
}
Update the view model.
// OverviewViewModel.kt
class OverviewViewModel : ViewModel() {
// ...
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
viewModelScope.launch {
_status.value = MarsApiStatus.LOADING
try {
_properties.value = MarsApi.retrofitService.getProperties(filter.value)
_status.value = MarsApiStatus.DONE
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
}
}
}
<!-- overflow_menu.xml -->
<menu ...>
<item
android:id="@+id/show_all_menu"
android:title="@string/show_all" />
<item
android:id="@+id/show_rent_menu"
android:title="@string/show_rent" />
<item
android:id="@+id/show_buy_menu"
android:title="@string/show_buy" />
</menu>
Then, update the view model when user choose the menu.
// OverviewFragment.kt
class OverviewFragment : Fragment() {
// ...
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel.updateFilter(
when (item.itemId) {
R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
else -> MarsApiFilter.SHOW_ALL
}
)
return true
}
}
Create a detail page with navigation
When the user taps the tile, pass the data to the detail page.
// DetailViewModel.kt
class DetailViewModel(marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
init {
_selectedProperty.value = marsProperty
}
}
Update the fragment_detail.xml
.
<ImageView
android:id="@+id/main_photo_image"
...
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}" />
<!-- ... -->
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
Then, add navigation with passing property.
// OverviewViewModel.kt
class OverviewViewModel : ViewModel() {
// ...
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}
}
Add click listeners in the grid adapter and fragment.
// PhotoGridAdapter.kt
class PhotoGridAdapter(private val onClickListener: OnClickListener) : ListAdapter<MarsProperty, PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
// ...
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}
// ...
class OnClickListener(val clickListener: (marsProperty: MarsProperty) -> Unit) {
fun onClick(marsProperty: MarsProperty) = clickListener(marsProperty)
}
}
Then, update the OverviewFragment.kt
.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})
MarsProperty
is unable to pass through the navigation yet. The class need to be Parcelable
via @parcelize
.
Open MarsProperty.kt
and add @parcelize
.
@Parcelize
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double
) : Parcelable {
val isRental
get() = type == "rent"
}
Add argument at detail fragment in nav_graph.xml
.
<fragment
android:id="@+id/detailFragment"
...>
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
</fragment>
Finally, add an observer to navigateToSelectedProperty
.
//OverviewFragment.kt
// in `onCreateView()`
viewModel.navigateToSelectedProperty.observe(this, Observer {
if (null != it) {
this.findNavController()
.navigate(OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
Add initial logic for the detail fragment.
// DetailFragment.kt
class DetailFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val application = requireNotNull(activity).application
val binding = FragmentDetailBinding.inflate(inflater)
binding.lifecycleOwner = this
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
binding.viewModel = ViewModelProvider(this, viewModelFactory).get(DetailViewModel::class.java)
return binding.root
}
}
To update the detail page, add these string resources in strings.xml
.
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
Then, add Transformations in the view model.
// DetailViewModel.kt
// In the detail viewmodel class
val displayPropertyPrice = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.display_price_monthly_rental
false -> R.string.display_price
}, it.price)
}
val displayPropertyType = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.type_rent
false -> R.string.type_sale
})
}
Add two textviews on the detail layout.
<TextView
android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
tools:text="To Rent" />
<TextView
android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
tools:text="$100,000" />
다음 챕터: Android Kotlin Fundamentals