How can I emit Result.Loading first before I query data and return the data of Result.Success in Android Studio? - kotlin

I use the following Code A to query records ,the data are wrapped with sealed class Result<out R>.
The val queryList is assigned with Result.Loading first, then it is assigned with Result.Success and wrapped data, the different UI will be loaded based the different value of queryList.
I think the queryList is only assigned with Result.Loading onetime, the queryList will keep return Result.Success when I launch mViewMode.listRecord() again and again, right?
So I hope the queryList is always assigned with Result.Loading before I launch mViewMode.listRecord() and return Result.Success , how can I fix the code?
Maybe do I need to modify Code B? or do I need to redesign data structure? or is there the better solution?
Code A
#Composable
fun Greeting() {
Column( ) {
val aResult: Result<Flow<List<MRecord>>> = Result.Loading
val queryList by produceState(initialValue = aResult) {
value = mViewMode.listRecord()
}
when (queryList){
is Result.Error -> { ...}
is Result.Loading -> { ... }
is Result.Success -> { ... }
}
}
}
class SoundViewModel #Inject constructor(...): ViewModel()
{
fun listRecord(): Result<Flow<List<MRecord>>>{
return aRecordRepository.listRecord()
}
}
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao){
fun listRecord(): Result<Flow<List<MRecord>>> {
val temp = mRecordDao.listRecord()
return Result.Success(temp)
}
}
interface RecordDao {
#Query("SELECT * FROM record_table ORDER BY createdDate desc")
fun listRecord(): Flow<List<MRecord>>
}
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
Code B
...
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao){
fun listRecord(): Result<Flow<List<MRecord>>> {
val temp = mRecordDao.listRecord()
return Result.Success(temp) //How can I return Result.Loading first, then return Result.Success(temp)?
}
}
...

You can create a StateFlow in your view model representing Result and connect it to your RecordRepository as follows and then convert it to compose state using collectAsState
#Composable
fun Greeting(soundViewModel: SoundViewModel = SoundViewModel()) {
LaunchedEffect(Unit) {
soundViewModel.listRecord()
}
Column {
val queryList: Result by soundViewModel.dataResult.collectAsState()
when (queryList) {
is Result.Error -> {
...
}
is Result.Loading -> {
...
}
is Result.Success -> {
...
}
}
}
}
class SoundViewModel {
private val _dataResult: MutableStateFlow<Result> = MutableStateFlow(Result.Loading) // private mutable state flow
val dataResult = _dataResult.asStateFlow() // publicly exposed as read-only state flow
private val recordRepository = RecordRepository()
suspend fun listRecord() {
recordRepository.listRecord().collect {
_dataResult.value = Result.Success(it)
}
}
}
class RecordRepository {
fun listRecord(): Flow<List<Int>> = flow {
emit(listOf(1))
delay(1000L)
emit(listOf(2, 3))
}
}
sealed interface Result {
object Loading : Result
data class Success(val lst: List<Int>) : Result
data class Error(val err: Throwable) : Result
}

The tricky thing is: When you expose a Flow from Room, it only emits each list after there's a database change and a new query is completed. There is no in-between signal from the flow to indicate that the database change is detected but the new query isn't completed yet.
One possible solution is if you create a flow in your repository that when something that happens modifies the database, it restarts with a new emission of Result.Loading and then emits the DAO flow again. This way, your Flow is protected from missing any changes, even if you somehow miss showing a loading state.
You could use a shared flow in the Repository if there's more than one flow you want to handle this way. Use it with flatMapLatest, so every time you do something that is likely to cause a database change, the existing upstream listRecord flow from the DAO will be cancelled so you can get a new Loading state before collecting it again.
Disclaimer: I haven't tested this. It's only an idea.
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao){
private expectedChangeTicker = MutableSharedFlow<Unit>(replay = 1, bufferOverflow = BufferOverflow.DROP_OLDEST)
suspend fun addSomething(someThing: SomeThing) {
// Call this in every repository function that might cause listRecord to change
expectedChangeTicker.emit(Unit)
mRecordDao.addSomething(someThing)
}
val listRecord: Flow<Result<List<MRecord>> =
expectedChangeTicker.flatMapLatest {
flow {
emit(Result.Loading)
emitAll(mRecordDao.listRecord().map { Result.Success(it) })
}
}
}
I don't know Compose, so I'm not sure how you should expose this Flow in your ViewModel. Notice I changed it from Result<Flow...> to Flow<Result...>, which I think is more likely what you need. Here is my guess at how it should be done:
class SoundViewModel #Inject constructor(...): ViewModel()
{
val listRecord: Flow<Result<List<MRecord>>> =
aRecordRepository.listRecord
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), replay = 1)
}
#Composable
fun Greeting() {
Column( ) {
val aResult: Result<List<MRecord>> = Result.Loading
val queryList by produceState(initialValue = Result.Loading) {
value = mViewMode.listRecord
}
when (queryList){
is Result.Error -> { ...}
is Result.Loading -> { ... }
is Result.Success -> { ... }
}
}
}

I don't think you need produceState. You can simply collect the flow returned by Dao in your composable using collectAsState() extension function.
#Composable
fun Greeting() {
Column( ) {
val queryList by viewModel.listRecord.collectAsState(Result.Loading)
when (queryList){
is Result.Error -> { ...}
is Result.Loading -> { ... }
is Result.Success -> { ... }
}
}
}
class SoundViewModel #Inject constructor(...): ViewModel() {
val listRecord = aRecordRepository.listRecord()
}
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao) {
fun listRecord(): Flow<List<MRecord>> {
return mRecordDao.listRecord()
}
}
Edit:
If you want to emit the loading state from the flow itself, you can do something like this:
class RecordRepository #Inject constructor(private val mRecordDao: RecordDao) {
fun listRecord(): Flow<Result<List<MRecord>>> {
return flow { // Create a new flow
emit(Result.Loading) // Emit loading state right away
mRecordDao.listRecord().collect {
emit(Result.Success(it)) // Emit success state upon receiving data from dao
}
}
}
}

Related

realTime List using callbackFlow from firestore

i'm facing hard times updating list of Orders in real time from firestore using stateflow !!
class RepositoryImp : Repository {
private fun Query.snapshotFlow(): Flow<QuerySnapshot> = callbackFlow {
val snapshott = addSnapshotListener { value, error ->
if (error != null) {
close()
return#addSnapshotListener
}
if (value != null)
trySend(value)
}
awaitClose {
snapshott.remove()
}
}
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot)) // **HERE** !!!!!!
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
}
i'm receiving the error from // emit(State.success(snapshot)) that says :
Type mismatch: inferred type is Flow<(Mutable)List<OrderModel!>> but List< OrderModel> was expected
sealed class State <T> {
class Loading <T> : State<T>()
data class Success <T> (val data: T) : State <T>()
data class Failed <T> (val message: String) : State <T>()
companion object {
fun <T> loading() = Loading <T>()
fun <T> success(data: T) = Success(data)
fun <T> failed(message: String) = Failed<T>(message)
}
}
My fun to LoadOrders :
private suspend fun loadOrders() {
viewModel.getAllOrders().collect { state ->
when (state) {
is State.Loading -> {
showToast("Loading")
}
is State.Success -> {
adapter.submitList(state.data)
}
is State.Failed -> showToast("Failed! ${state.message}")
}
}
}
Your snapshot variable is a Flow of lists, not a single List. If you want to just fetch the current list, you shouldn't use a flow for that. Instead use get().await().
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.get().await()
.let { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot))
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
The flowOn call is actually unnecessary because we aren't doing anything blocking. await() is a suspend function.
Based on comments discussion below, supposing we want to show a loading state only before the first item, then show a series of success states, and we want to show an error and stop emitting once there's an error, we could do:
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshots = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { State.success(it.toObjects(OrderModel::class.java)) }
emitAll(snapshots)
}.catch {
emit(State.failed(it.message.toString()))
}

How to handle Kotlin Jetpack Paging 3 exceptions?

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.
This is the Repository:
class GitRepoRepository(private val service: GitRepoApi) {
fun getListData(): LiveData<PagingData<GitRepo>> {
return Pager(
// Configuring how data is loaded by adding additional properties to PagingConfig
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = {
// Here we are calling the load function of the paging source which is returning a LoadResult
GitRepoPagingSource(service)
}
).liveData
}
}
This is the ViewModel:
class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()
suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
val response = repository.getListData().cachedIn(viewModelScope)
_gitReposList.value = response.value
return response
}
}
In the Activity I am doing:
lifecycleScope.launch {
gitRepoViewModel.getAllGitRepos().observe(this#PagingActivity, {
recyclerViewAdapter.submitData(lifecycle, it)
})
}
And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.
Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).
class GitRepoPagingSource(..): PagingSource<..>() {
...
override suspend fun load(..): ... {
try {
... // Logic to load data
} catch (retryableError: IOException) {
return LoadResult.Error(retryableError)
}
}
}
This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
You gotta pass your error handler to the PagingSource
class MyPagingSource(
private val api: MyApi,
private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
try {
...
} catch(e: Exception) {
onError(e) // <-- pass your error listener here
}
}
}

How to ignore empty database result for the first time and wait for server result in application?

My app using room as a database and retrofit as a network calling api.
i am observing database only as a single source of truth. every thing is working fine. But i am not finding solution of one scenario.
Like for the first time when user open app it do following operations
fetch data from db
fetch data from server
because currently database is empty so it sends empty result to observer which hide progress bar . i want to discard that event and send result to observer when server dump data to database. even server result is empty. so progress bar should always hide once their is confirmation no data exists.
in other words application should always rely on database but if it empty then it should wait until server response and then notify observer.
this is my code
observer
viewModel.characters.observe(viewLifecycleOwner, Observer {
Log.e("status is ", "${it.message} at ${System.currentTimeMillis()}")
when (it.status) {
Resource.Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
if (!it.data.isNullOrEmpty()) adapter.setItems(ArrayList(it.data))
}
Resource.Status.ERROR -> {
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
binding.progressBar.visibility = View.GONE
}
Resource.Status.LOADING ->
binding.progressBar.visibility = View.VISIBLE
}
})
ViewModel
#HiltViewModel
class CharactersViewModel #Inject constructor(
private val repository: CharacterRepository
) : ViewModel() {
val characters = repository.getCharacters()
}
Repository
class CharacterRepository #Inject constructor(
private val remoteDataSource: CharacterRemoteDataSource,
private val localDataSource: CharacterDao
) {
fun getCharacters() : LiveData<Resource<List<Character>>> {
return performGetOperation(
databaseQuery = { localDataSource.getAllCharacters() },
networkCall = { remoteDataSource.getCharacters() },
saveCallResult = { localDataSource.insertAll(it.results) }
)
}
}
Utility function for all api and database handling
fun <T, A> performGetOperation(databaseQuery: () -> LiveData<T>,
countQuery: () -> Int,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery().map { Resource.success(it,"database") }.distinctUntilChanged()
emitSource(source)
val responseStatus = networkCall()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
}
}
LocalDataSource
#Dao
interface CharacterDao {
#Query("SELECT * FROM characters")
fun getAllCharacters() : LiveData<List<Character>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(characters: List<Character>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(character: Character)
}
DataSource
class CharacterRemoteDataSource #Inject constructor(
private val characterService: CharacterService
): BaseDataSource() {
suspend fun getCharacters() = getResult { characterService.getAllCharacters() }}
}
Base Data Source
abstract class BaseDataSource {
protected suspend fun <T> getResult(call: suspend () -> Response<T>): Resource<T> {
try {
Log.e("status is", "started")
val response = call()
if (response.isSuccessful) {
val body = response.body()
if (body != null) return Resource.success(body,"server")
}
return error(" ${response.code()} ${response.message()}")
} catch (e: Exception) {
return error(e.message ?: e.toString())
}
}
private fun <T> error(message: String): Resource<T> {
Timber.d(message)
return Resource.error("Network call has failed for a following reason: $message")
}
}
Character Service
interface CharacterService {
#GET("character")
suspend fun getAllCharacters() : Response<CharacterList>
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T,message : String): Resource<T> {
return Resource(Status.SUCCESS, data, message)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, "loading")
}
}
}
CharacterList
data class CharacterList(
val info: Info,
val results: List<Character>
)
What is the best way by that i ignore database if it is empty and wait for server response and then notify observer

My first observer called correctly but another was not called after inserted data to room database in kotlin android

In the application, I am fetching data from the web and from the observer change method, Insert that data to local db. that's fine. but after inserted to db, My second observer not called so my UI will not update.
ManActivity.class
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var adapter: MainAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout.activity_main)
setupViewModel()
setupUI()
setupObservers()
setupObservers2()
}
private fun setupViewModel() {
viewModel = ViewModelProviders.of(
this,
ViewModelFactory(ApiHelper(RetrofitBuilder.apiService))
).get(MainViewModel::class.java)
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = MainAdapter(arrayListOf())
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObservers() {
viewModel.getUsers().observe(this, Observer {
//viewModel.getUserFromWeb()
it?.let { resource ->
when (resource.status) {
SUCCESS -> {
Log.d("MYLOG","MyAPIChange success")
recyclerView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
resource.data?.let {
users -> viewModel.setUserListToDB(this,users)
//sleep(1000)
}
}
ERROR -> {
recyclerView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
Log.d("MYLOG","MyAPIChange error")
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
LOADING -> {
Log.d("MYLOG","MyAPIChange loading")
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
}
}
})
}
private fun setupObservers2() {
viewModel.getUserFromDB(this).observe(this, Observer {
users -> retrieveList(users)
Log.d("MYLOG","..MyDBChange")
})
}
private fun retrieveList(users: List<User>) {
adapter.apply {
addUsers(users)
notifyDataSetChanged()
}
}
}
MyViewModel.class
class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {
//lateinit var tempUser : MutableLiveData<List<User>>
fun getUsers() = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
emit(Resource.success(data = mainRepository.getUsers()))
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
//emit(mainRepository.getUsers()) //direct call
}
fun getUserFromDB(context: Context) = liveData(Dispatchers.IO) {
emit(mainRepository.getUserList(context))
}
fun setUserListToDB(context: Context, userList: List<User>) {
/*GlobalScope.launch {
mainRepository.setUserList(context, userList)
}*/
CoroutineScope(Dispatchers.IO).launch {
mainRepository.setUserList(context, userList)
}
}
}
MyRepository.class
class MainRepository(private val apiHelper: ApiHelper) {
suspend fun getUsers() = apiHelper.getUsers() // get from web
companion object {
var myDatabase: MyDatabase? = null
lateinit var userList: List<User>
fun initializeDB(context: Context): MyDatabase {
return MyDatabase.getDataseClient(context)
}
/*fun insertData(context: Context, username: String, password: String) {
myDatabase = initializeDB(context)
CoroutineScope(Dispatchers.IO).launch {
val loginDetails = User(username, password)
myDatabase!!.myDao().InsertData(loginDetails)
}
}*/
}
//fun getUserList(context: Context, username: String) : LiveData<LoginTableModel>? {
suspend fun getUserList(context: Context) : List<User> {
myDatabase = initializeDB(context)
userList = myDatabase!!.myDao().getUserList()
Log.d("MYLOG=", "DBREAD"+userList.size.toString())
return userList
}
fun setUserList(context: Context,userList: List<User>){
myDatabase = initializeDB(context)
/*CoroutineScope(Dispatchers.IO).launch {
myDatabase!!.myDao().InsertAllUser(userList)
Log.d("MYLOG","MyDBInserted")
}*/
myDatabase!!.myDao().InsertAllUser(userList)
Log.d("MYLOG","MyDBInserted")
/*val thread = Thread {
myDatabase!!.myDao().InsertAllUser(userList)
}
Log.d("MYLOG","MyDBInserted")
thread.start()*/
}
}
DAO class
#Dao
interface DAOAccess {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun InsertAllUser(userList: List<User>)
// #Query("SELECT * FROM User WHERE Username =:username")
// fun getLoginDetails(username: String?) : LiveData<LoginTableModel>
#Query("SELECT * FROM User")
suspend fun getUserList() : List<User>
}
RetrofitBuilder
object RetrofitBuilder {
private const val BASE_URL = "https://5e510330f2c0d300147c034c.mockapi.io/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: ApiService = getRetrofit().create(ApiService::class.java)
}
Please can you know that what I am doing wrong here and why the second observer was not called after insert to db
Actually It was called when the screen launched but that time data not inserted so list size was 0 and after insert data this method will not call again. But Once I close app and again start then data will display bcoz at launch time, this method call and data got
I don't have enough reputation to commet, therefore I just bring a suggestion in this answer:
Suggestion/Solution
Room supports LiveData out of the box. So in your DAO you can change
suspend fun getUserList() : List<User>
to
suspend fun getUserList() : LiveData<List<User>>
Then in your repository adjust to
suspend fun getUserList(context: Context) : LiveData<List<User>> {
myDatabase = initializeDB(context)
userList = myDatabase!!.myDao().getUserList()
Log.d("MYLOG=", "DBREAD"+userList.value.size.toString())
return userList
}
and in the ViewModel
fun getUserFromDB(context: Context) = mainRepository.getUserList(context))
With these adjustments I think it should work.
Explaination
You used the liveData couroutines builder here
fun getUserFromDB(context: Context) = liveData(Dispatchers.IO) {
emit(mainRepository.getUserList(context))
}
As far as I understand this builder, it is meant to execute some asynchronous/suspend task and as soon as this task finishes the liveData you created will emit the result. That means that you only once receive the state of the user list an emidiately emit the list to the observer one single time and then this liveData is done. It does not observe changes to the list in the DB the whole time.
That is why it works perfectly for observing the API call (you want to wait until the call is finished and emit the response one single time), but not for observing the DB state(you want to observe the user list in the DB all the time and emit changes to the observer whenever the list is changed)

MutableLiveData for collections

I request data from server by bunches and store it in the array.To track fetching of the next bunch of the data I have this class.In the addItems method I notify diffObservers and pass list of new items:
class PackItems:MutableLiveData<ArrayList<GetPacksResponse.PackData>>() {
private var diffObservers=ArrayList<Observer<List<GetPacksResponse.PackData>>>()
private var active=false
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
super.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
override fun removeObservers(owner: LifecycleOwner) {
super.removeObservers(owner)
diffObservers= ArrayList()
}
fun addItems(toAdd:List<GetPacksResponse.PackData>) {
value?.addAll(toAdd)
if (active)
for (observer in diffObservers)
observer.onChanged(toAdd)
}
override fun onActive() {
super.onActive()
active=true
}
override fun onInactive() {
super.onInactive()
active=false
}
}
The problem is PackItems is MutableLiveData and it's not good practice to expose it.Is there way to cast it to LiveData?Like usually we do:
private val _items = MutableLiveData<List<Int>>()
val items: LiveData<List<Int>> = _items
UPD:Ideally would be if I could expose completely immutable LiveData.But I can't just write
private val _packs:PackItems=PackItems()
val packs:LiveData<ArrayList<GetPacksResponse.PackData>>
get()=_packs
Because in this case packs won't contain observeItems method.Therefore there must be custom class derived from LiveData like:
open class PackItems: LiveData<ArrayList<GetPacksResponse.PackData>>() {
protected var active=false
protected var diffObservers = ArrayList<Observer<List<GetPacksResponse.PackData>>>()
fun observeItems(owner: LifecycleOwner, valueObserver: Observer<List<GetPacksResponse.PackData>>, diffObserver: Observer<List<GetPacksResponse.PackData>>) {
super.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
//...
}
class MutablePackItems: PackItems() {
fun addItems(toAdd:List<GetPacksResponse.PackData>) {
value?.addAll(toAdd)
if (active)
for (observer in diffObservers)
observer.onChanged(toAdd)
}
}
But in this case I won't be able to set data because now MutablePackItems is LiveData(immutable) :)
I'd consider using composition instead of inheritance:
class PackItems() {
private val mutableData = MutableLiveData<ArrayList<GetPacksResponse.PackData>>()
val asLiveData: LiveData<ArrayList<GetPacksResponse.PackData>> get() = mutableData
...
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
mutableData.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
fun removeObservers(owner: LifecycleOwner) {
mutableData.removeObservers(owner)
diffObservers = ArrayList()
}
// etc
}
EDIT: to set active as in your original code, may be a bit nastier:
private val mutableData = object : MutableLiveData<ArrayList<GetPacksResponse.PackData>>() {
override fun onActive() {
super.onActive()
active = true
}
override fun onInactive() {
super.onInactive()
active = false
}
}
EDIT 2:
but the main problem is I need to return custom LiveData class with custom observeItems method
The point is that you don't necessarily. Whenever you'd call LiveData's method (e.g. observe), just call items.asLiveData.observe(...) instead. If you want to pass it to another method foo accepting LiveData, call foo(items.asLiveData).
In principle, you could modify this approach by extending LiveData and delegating all calls to mutableData:
class PackItems(): LiveData<ArrayList<GetPacksResponse.PackData>>() {
private val mutableData = MutableLiveData<ArrayList<GetPacksResponse.PackData>>()
...
fun observeItems(owner: LifecycleOwner, valueObserver:Observer<List<GetPacksResponse.PackData>>,diffObserver:Observer<List<GetPacksResponse.PackData>>) {
mutableData.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
override fun observe(owner: LifecycleOwner, observer: ArrayList<GetPacksResponse.PackData>) {
mutableData.observe(owner, observer)
}
override fun removeObservers(owner: LifecycleOwner) {
mutableData.removeObservers(owner) // not super!
diffObservers = ArrayList()
}
// etc
}
but I don't think it's a good idea.