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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"

android {
compileSdkVersion 29
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/sfjava/tunesfeed/ViewModelFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.savedstate.SavedStateRegistryOwner
import com.sfjava.tunesfeed.data.source.FeedItemsRepository
import com.sfjava.tunesfeed.ui.feeds.FeedListViewModel
import com.sfjava.tunesfeed.ui.feeditem.FeedItemViewModel
import com.sfjava.tunesfeed.ui.feedlist.FeedListViewModel

/**
* Factory for all ViewModels.
Expand All @@ -26,6 +27,8 @@ class ViewModelFactory constructor(
when {
isAssignableFrom(FeedListViewModel::class.java) ->
FeedListViewModel(itemsRepository) //, handle) // FIXME: handle state persistence?
// isAssignableFrom(FeedItemViewModel::class.java) ->
// FeedItemViewModel(feedListViewModel) //, handle) // FIXME: handle state persistence?
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
Expand Down
44 changes: 44 additions & 0 deletions app/src/main/java/com/sfjava/tunesfeed/ui/Event.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.sfjava.tunesfeed.ui

import androidx.lifecycle.Observer

/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {

@Suppress("MemberVisibilityCanBePrivate")
var hasBeenHandled = false
private set // allow external read but not write

/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}

/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

/**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let {
onEventUnhandledContent(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sfjava.tunesfeed.ui.feeditem

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.sfjava.tunesfeed.data.model.FeedType
import com.sfjava.tunesfeed.databinding.FeedItemDetailFragmentBinding
import com.sfjava.tunesfeed.ui.feeditem.FeedItemDetailFragmentArgs.Companion.fromBundle
import com.sfjava.tunesfeed.ui.getViewModelFactory

class FeedItemDetailFragment(): Fragment() {

private val itemId by lazy {
arguments?.let { fromBundle(it).itemId } ?: throw IllegalArgumentException("Expected arguments")
}

private val feedItemViewModel
by viewModels<FeedItemViewModel> { getViewModelFactory(FeedType.ComingSoon) } // FIXME

private lateinit var viewDataBinding: FeedItemDetailFragmentBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = FeedItemDetailFragmentBinding.inflate(inflater, container, false).apply {
viewmodel = feedItemViewModel
}
return viewDataBinding.root
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.sfjava.tunesfeed.ui.feeditem

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sfjava.tunesfeed.data.model.FeedItem
import com.sfjava.tunesfeed.ui.feedlist.FeedListViewModel

class FeedItemViewModel(val feedListViewModel: FeedListViewModel): ViewModel() {

private val _item = MutableLiveData<FeedItem>()
val item: LiveData<FeedItem> = _item

fun getItem(id: String) = feedListViewModel.getItem(id)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sfjava.tunesfeed.ui.feeds
package com.sfjava.tunesfeed.ui.feedlist

import android.view.LayoutInflater
import android.view.ViewGroup
Expand All @@ -24,7 +24,7 @@ class FeedListAdapter(private val viewModel: FeedListViewModel) :
RecyclerView.ViewHolder(binding.root) {

fun bind(viewModel: FeedListViewModel, item: FeedItem) {
// binding.viewmodel = viewModel // NOTE: plumb-through if we need to ref the list-model
binding.feedListViewModel = viewModel // NOTE: need to ref this for item on-click action
binding.item = item
binding.executePendingBindings()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sfjava.tunesfeed.ui.feeds
package com.sfjava.tunesfeed.ui.feedlist

import android.widget.ImageView
import androidx.databinding.BindingAdapter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sfjava.tunesfeed.ui.feeds
package com.sfjava.tunesfeed.ui.feedlist

import android.os.Bundle
import android.util.Log
Expand All @@ -7,24 +7,26 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.sfjava.tunesfeed.data.model.FeedType
import com.sfjava.tunesfeed.databinding.FragmentFeedListBinding
import com.sfjava.tunesfeed.databinding.FeedListFragmentBinding
import com.sfjava.tunesfeed.ui.EventObserver
import com.sfjava.tunesfeed.ui.getViewModelFactory

class FeedListFragment : Fragment() {

private val feedListViewModel
by viewModels<FeedListViewModel> { getViewModelFactory(arguments?.get("feedType") as FeedType) }

private lateinit var viewDataBinding: FragmentFeedListBinding
private lateinit var viewDataBinding: FeedListFragmentBinding
private lateinit var listAdapter: FeedListAdapter

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = FragmentFeedListBinding.inflate(inflater, container, false).apply {
viewDataBinding = FeedListFragmentBinding.inflate(inflater, container, false).apply {
viewmodel = feedListViewModel
}
return viewDataBinding.root
Expand All @@ -33,13 +35,22 @@ class FeedListFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
viewDataBinding.lifecycleOwner = viewLifecycleOwner
val viewModel = viewDataBinding.viewmodel
if (viewModel != null) {
listAdapter = FeedListAdapter(viewModel)
viewDataBinding.itemsList.adapter = listAdapter
} else {
Log.w("TunesFeed", "ViewModel not initialized when attempting to set up adapter.")
}

viewModel?.showItemDetailEvent?.observe(viewLifecycleOwner, EventObserver {
showItemDetail(it)
})
}

private fun showItemDetail(id: String) {
val action = FeedListFragmentDirections.actionFeedListFragmentToFeedItemDetailFragment(id)
findNavController().navigate(action)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.sfjava.tunesfeed.ui.feeds
package com.sfjava.tunesfeed.ui.feedlist

import androidx.lifecycle.*
import com.sfjava.tunesfeed.data.model.FeedItem
import com.sfjava.tunesfeed.data.source.FeedItemsRepository
import com.sfjava.tunesfeed.data.source.Result
import com.sfjava.tunesfeed.ui.Event
import kotlinx.coroutines.launch

class FeedListViewModel(val itemsRepository: FeedItemsRepository) : ViewModel() {
Expand All @@ -22,6 +23,9 @@ class FeedListViewModel(val itemsRepository: FeedItemsRepository) : ViewModel()
}
val items: LiveData<List<FeedItem>> = _items

private val _showItemDetailEvent = MutableLiveData<Event<String>>()
val showItemDetailEvent: LiveData<Event<String>> = _showItemDetailEvent

private val _dataLoading = MutableLiveData<Boolean>()
val dataLoading: LiveData<Boolean> = _dataLoading

Expand All @@ -39,6 +43,12 @@ class FeedListViewModel(val itemsRepository: FeedItemsRepository) : ViewModel()
_forceUpdate.value = forceUpdate
}

fun getItem(id: String): FeedItem? = _items.value?.filter { it.id == id }?.firstOrNull()

fun showItemDetail(id: String) {
_showItemDetailEvent.value = Event(id)
}

private fun filterItems(itemsResult: Result<List<FeedItem>>): LiveData<List<FeedItem>> {
// TODO: this is a good case for liveData builder; replace when stable (per google's sample)
val result = MutableLiveData<List<FeedItem>>()
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
59 changes: 59 additions & 0 deletions app/src/main/res/layout/feed_item_detail_fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
<variable
name="viewmodel"
type="com.sfjava.tunesfeed.ui.feeditem.FeedItemViewModel" />
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="4dp">

<ImageView
android:id="@+id/product_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="125dp"
android:scaleType="centerCrop"
android:adjustViewBounds ="true"
app:imageUrl="@{viewmodel.item.artworkUrl100}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:text="@{viewmodel.item.name}"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />

<TextView
android:id="@+id/title_text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:text="@{viewmodel.item.artistName}"
android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />

</LinearLayout>

</LinearLayout>
</layout>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<import type="android.view.View" />
<variable
name="viewmodel"
type="com.sfjava.tunesfeed.ui.feeds.FeedListViewModel" />
type="com.sfjava.tunesfeed.ui.feedlist.FeedListViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<variable
name="item"
type="com.sfjava.tunesfeed.data.model.FeedItem" />

<variable
name="feedListViewModel"
type="com.sfjava.tunesfeed.ui.feedlist.FeedListViewModel" />
</data>

<LinearLayout
Expand All @@ -14,8 +18,8 @@
android:orientation="horizontal"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="4dp">
<!-- android:onClick="@{() -> viewmodel.openTask(task.id)}">-->
android:paddingBottom="4dp"
android:onClick="@{() -> feedListViewModel.showItemDetail(item.id)}">

<ImageView
android:id="@+id/product_image"
Expand Down
Loading