1

I have a LiveData object which i'm observing within a fragment within an activity. The activity listens to broadcasts from the system via 2 broadcast receivers. The data is queried from room according to the current date. So the receivers detect if the date was changed and if so, go and refresh the query.

Problem is after i change the date in the settings and come back to the app, the observer isn't triggered. If i go to another fragment and return the data will change, but not from the

Now for the code:

Activity:

     private lateinit var timeReceiver: MyTimeReceiver

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        timeReceiver = MyTimeReceiver()
        registerReceiver(timeReceiver, filter)
        registerReceiver(refreshReceiver, IntentFilter("refresh_data"))
        mainFragment = MainFragment.newInstance()

       }

 private val refreshReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == "refresh_data") {
                Timber.e("time changed, need to refresh data")                         
                mainFragment.refreshData()
            }
        }
    }

 override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeReceiver)
        unregisterReceiver(refreshReceiver)
    }

Fragment:

private var counter: Int = 0
private lateinit var viewModel: MainFragmentViewModel
private lateinit var date: String

companion object {
    fun newInstance(): MainFragment {
        return MainFragment()
    }
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.fragment_main, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    plus_btn.setOnClickListener { addCup() }
    minus_btn.setOnClickListener { decreaseCup() }
    viewModel = ViewModelProviders.of(this).get(MainFragmentViewModel::class.java)
    viewModel.dataList.observe(viewLifecycleOwner, Observer {
            it.let { it1 ->
                counter = it1.size
                cup_counter.text = "$counter"
                setGoalVisibility()
            }
    })
}

override fun onResume() {
    super.onResume()
    viewModel.checkCups(setDate())
}

private fun decreaseCup() {
    viewModel.deleteCup(counter, date)
}

private fun addCup() {
    Timber.e(date)
    viewModel.insertCup(
        WaterCupEntity(
            System.currentTimeMillis(),
            ++counter,
            date
        )
    )
}
fun refreshData() {
    viewModel.checkCups(setDate())
}

private fun setDate(): String {
    val dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.US)
    date = dateFormat.format(Calendar.getInstance().time)
    return date
}

ViewModel:

class MainFragmentViewModel(application: Application) : AndroidViewModel(application) {

private var dao: WaterCupDao
var dataList: LiveData<List<WaterCupEntity>>
private var job = Job()
private val coroutineContext: CoroutineContext
    get() = job + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.US)
var date: String

init {
    dao = MyCupsDb.getInstance(application, scope).getDao()
    date = dateFormat.format(Calendar.getInstance().time)
    Timber.e("getting all cups for date: %s", date)
    dataList = dao.getAllCupsWithSameDate(date)
}

fun checkCups(date :String) {
    dataList = dao.getAllCupsWithSameDate(date)
}

fun insertCup(cup: WaterCupEntity) = runBlocking {
    scope.launch(Dispatchers.IO) {
        dao.insert(cup)
    }
}

fun deleteCup(number: Int, date: String) =
    scope.launch(Dispatchers.IO) {
        dao.deleteCupForDate(number, date)
    }

fun deleteToday(date :String) {
    scope.launch (Dispatchers.IO){
        dao.deleteAllCupsForDate(date)
    }
}

fun deleteAllCups() {
    scope.launch (Dispatchers.IO){
        dao.deleteAllCups()
    }
}
}

Room db :

@Database(entities = [WaterCupEntity::class], version = 1)
abstract class MyCupsDb : RoomDatabase() {
abstract fun getDao(): WaterCupDao

companion object {

    @Volatile
    private var instance: MyCupsDb? = null


    fun getInstance(context: Context, scope:CoroutineScope): MyCupsDb {
        return instance ?: synchronized(this) {
            instance ?: buildDb(context,scope).also { instance = it }
        }
    }

    private fun buildDb(context: Context,scope:CoroutineScope): MyCupsDb {
        return Room.databaseBuilder(context, MyCupsDb::class.java, "myDb")
            .addCallback(WordDatabaseCallback(scope))
            .build()
    }
}

private class WordDatabaseCallback(
    private val scope: CoroutineScope

) : RoomDatabase.Callback() {

    override fun onCreate(db: SupportSQLiteDatabase) {
        super.onOpen(db)
        instance?.let { database ->
            scope.launch(Dispatchers.IO) {
                populateDatabase(database.getDao())
            }
        }
    }

    fun populateDatabase(dao: WaterCupDao) {
        var word = WaterCupEntity(System.currentTimeMillis(),1,"11.01.2019")
        dao.insert(word)
        word = WaterCupEntity(System.currentTimeMillis(),2,"11.01.2019")
        dao.insert(word)
    }
}
}

MyTimeReceiver:

class MyTimeReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
    when (intent.action){
        Intent.ACTION_TIMEZONE_CHANGED,
        Intent.ACTION_DATE_CHANGED,
        Intent.ACTION_TIME_CHANGED,
        Intent.ACTION_TIME_TICK -> context.sendBroadcast(Intent("refresh_data"))
    }
}
}
peresisUser
  • 1,680
  • 18
  • 23

1 Answers1

1

So mainly the use case was to trigger the LiveData query after some event - in this case an intent from a BroadcastReceiver - so it is refreshed. I wanted to put new params for the query and then make the query work.

What i didn't realize is this is basically against the design of LiveData. LiveData is data that you subscribe to, a client that subscribes to that data doesn't affect it, it merely listens to its changes. So the problem here is with design.

To get a new query you usually need either a new data (so your observe will trigger) or resubscribe to the LiveData - the harder and more complicated approach since then you need to manage the subscriptions so you don't leak. I chose to get a new data via using an insert once i get an intent. If anyone so wishes i can post the fixed code for this.

peresisUser
  • 1,680
  • 18
  • 23