2

I'm currently trying to work on an app that involves data coming from WearOS watches to an Android phone. When it gets to the phone, I'd like to chart the data (I'm currently trying to use MP Android Chart for this).

I'm using mutableStateListOf to attempt to watch changes to this list, as shown below:

private val repDataPoints = mutableStateListOf<RepDataPoint>()

RepDataPoint is a data class containing an x value and a boolean.

I send the data from the watch all at once, after it is done being collected. Then, I add it to this list. This appears to be working, as logcat in Android Studio shows the list is not staying at size 0. That code is here:

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        for (event in dataEvents) {
            if (event.type == DataEvent.TYPE_CHANGED && event.dataItem.uri.path == "/sensor_data") {
                //Keys are x, values are "isRep"
                val dataMapItem = DataMapItem.fromDataItem(event.dataItem)
                val dataPoints = dataMapItem.dataMap.getDataMapArrayList("dataPoints")

                //Debug - did we get the data?
                Log.d("PhoneApp", "Data received with ${dataPoints?.size} data points")

                dataPoints?.forEach { dataMap ->
                    val x = dataMap.getFloat("x")
                    val isRep = dataMap.getBoolean("isRep")
                    //Debug - what points did we get?
                    Log.d("PhoneApp", "Received data - X: $x, isRep: $isRep")
                    repDataPoints.add(RepDataPoint(x, isRep))
                }
            }

        }
    }

Here's where I want to use the data, in a composable that will hopefully show the graph:

@Composable
fun LineChartCompose(data: List<RepDataPoint>) {

    AndroidView(factory = { context ->
        LineChart(context).apply {
            // Log the size of the data list
            Log.d("LineChartCompose", "Data size: ${data.size}")

            val entries = data.mapIndexed { index, point ->
                Log.d("LineChartCompose", "Data point: index=$index, x=${point.x}, isRep=${point.isRep}")
                Entry(index.toFloat(), point.x)
            }

            val dataSet = LineDataSet(entries, "Data").apply {
                color = Color.BLUE
                valueTextColor = Color.BLACK
                setDrawValues(false)
                setDrawCircles(true)
                circleRadius = 4f
                setCircleColor(Color.BLUE)
                circleHoleColor = Color.BLUE
            }

            val lineData = LineData(dataSet)
            this.data = lineData

            description.isEnabled = false
            setTouchEnabled(true)
            isDragEnabled = true
            setScaleEnabled(true)
            setPinchZoom(true)
            axisRight.isEnabled = false
            xAxis.position = XAxis.XAxisPosition.BOTTOM

            Log.d("LineChartCompose", "Chart invalidated")
            invalidate()
        }
    })
}

These log statements only show up once, at the app's creation (which currently only shows this composable), with a list size of 0. I've tried reading up on mutableStateListOf, but everything I'm seeing says that adding to the list would qualify as modifying it, which would notify any composables that rely on it (like this one). The only thing I'm not sure about is that the documents say that those composables must also read the values, but I think I'm doing that here.

The list at the end of onDataChanged was 92 elements long, but still, no change was triggered. I'm still pretty new to Android. Am I making some stupid mistake? Is there a way I could reorganize my data to make better sure the composable starts drawing a graph when it gets data?

Any help is appreciated, and being new, any advice on how to write my code better is also welcome!

I sent data from the watch, logged it as it got to the phone, and made sure the list was being modified. Recomposition does not appear to be occurring, so I'm not getting a graph to draw.

2
  • Where did you exactly define repDataPoints ? In a ViewModel?
    – Yamin
    Commented Jun 11 at 19:50
  • Sorry when I first responded to this, I initially added the wrong thing: it's immediately after the MainActivity's opening brackets, just before OnCreate.
    – Bryn
    Commented Jun 11 at 20:10

2 Answers 2

1

you can try using StateFlow something like this: Your ViewModel

private val _repDataPoints = MutableStateFlow<List<RepDataPoint>>(emptyList())
val repDataPoints = _repDataPoints.asStateFlow()

Your Composable:

val list by viewModel.repDataPoints.collectAsStateWithLifecycle()

And finally to modify repDataPoints value you can do something like that:

fun addNewValue() {
    _repDataPoints.update { it + RepDataPoint() }
}

I think the problem is your list that you have in you viewmodel cant notify to your composable and never recompose

3
  • 1
    You do not need a coroutine, instead of emit you can directly set the value property. You shouldn't do either, though, when the new value depends on the old value; in that case you should use update and put everything in its lambda. Also you do not need toMutableList, just use the + operator.
    – tyg
    Commented Jun 11 at 21:39
  • was an example, but I do agree with you that this is also a refactored solution
    – witodev
    Commented Jun 11 at 21:42
  • Thank you! I actually don't have a view model yet, which is why this is so perplexing. I was going to just see if the graphing was working in the MainActivity, and then set up a view model, but I wasn't expecting recomposing to be an issue. So the list is inside my MainActivity, and since it's given as a parameter to a function, I thought JPC would recompose whenever it was changed? I will definitely move to flows and VMs, I just wanted to see if I could get something baseline working.
    – Bryn
    Commented Jun 11 at 21:43
0

Okay, I'm not positive that this was the only thing going wrong (because I also just gave up on trying to make this work and switched to flows to make it easier), but I've learned something today! As you can see, in the part where I try to make a graph, I used an AndroidView (a way to use views in Compose if you have to). From docs about AndroidView, found here: https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/views-in-compose

...AndroidView also provides an update callback that is called when the view is inflated. The AndroidView recomposes whenever a State read within the callback changes.

So I attempted to implement the callback method. Here is a brief example:

AndroidView(
        factory = { context ->
            LineChart(context).apply {
            }
        },
        update = { lineChart ->
            val entries = repDataPoints.mapIndexed { index, point ->
                Entry(index.toFloat(), point.x)
            }

And the code continued from there pretty similar to how it was before.

At first, even with flows, nothing was recomposing. But when I added the update callback, recomposition triggered and I got my graph. Hopefully this helps someone else!

This was...too many hours for such a simple task.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.