Skip to content

Commit cc41a05

Browse files
authored
Fix occasional crashes when stopping chart activity (#3929)
Despite compression, chart data size still can be too large, thus change the approach: instead of caching the data in the saved instance state, cache it in a retained fragment. Signed-off-by: Danny Baumann <[email protected]>
1 parent 5aef4e7 commit cc41a05

File tree

2 files changed

+22
-49
lines changed

2 files changed

+22
-49
lines changed

mobile/src/main/java/org/openhab/habdroid/ui/ChartWidgetActivity.kt

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import android.widget.Button
2828
import android.widget.TextView
2929
import androidx.core.graphics.ColorUtils
3030
import androidx.core.view.isVisible
31+
import androidx.fragment.app.Fragment
32+
import androidx.fragment.app.commitNow
3133
import androidx.lifecycle.lifecycleScope
3234
import com.github.mikephil.charting.charts.LineChart
3335
import com.github.mikephil.charting.components.AxisBase
@@ -80,17 +82,13 @@ import org.openhab.habdroid.model.withValue
8082
import org.openhab.habdroid.util.HttpClient
8183
import org.openhab.habdroid.util.ItemClient
8284
import org.openhab.habdroid.util.appendQueryParameter
83-
import org.openhab.habdroid.util.compress
8485
import org.openhab.habdroid.util.determineDataUsagePolicy
85-
import org.openhab.habdroid.util.extractParcelable
8686
import org.openhab.habdroid.util.map
8787
import org.openhab.habdroid.util.orDefaultIfEmpty
8888
import org.openhab.habdroid.util.parcelable
8989
import org.openhab.habdroid.util.resolveThemedColor
9090
import org.openhab.habdroid.util.resolveThemedColorArray
9191
import org.openhab.habdroid.util.serializable
92-
import org.openhab.habdroid.util.toByteArray
93-
import org.openhab.habdroid.util.uncompress
9492

9593
class ChartWidgetActivity : AbstractBaseActivity() {
9694
private lateinit var widget: Widget
@@ -101,9 +99,10 @@ class ChartWidgetActivity : AbstractBaseActivity() {
10199
private lateinit var errorText: TextView
102100
private lateinit var retryButton: Button
103101
private lateinit var seriesColors: Array<Int>
102+
private val dataCacheFragment get() =
103+
supportFragmentManager.findFragmentByTag("cache") as? ChartDataCacheFragment
104104
private var period: TemporalAmount = Duration.ofDays(1)
105105
private var serverFlags: Int = 0
106-
private var loadedChartData: ChartData? = null
107106

108107
override fun onCreate(savedInstanceState: Bundle?) {
109108
super.onCreate(savedInstanceState)
@@ -125,7 +124,12 @@ class ChartWidgetActivity : AbstractBaseActivity() {
125124
if (savedInstanceState != null) {
126125
savedInstanceState.serializable<Period>(PERIOD)?.let { period = it }
127126
savedInstanceState.serializable<Duration>(PERIOD)?.let { period = it }
128-
loadedChartData = savedInstanceState.getByteArray(DATA)?.uncompress()?.extractParcelable()
127+
}
128+
129+
if (dataCacheFragment == null) {
130+
supportFragmentManager.commitNow {
131+
add(ChartDataCacheFragment(), "cache")
132+
}
129133
}
130134

131135
seriesColors = resolveThemedColorArray(R.attr.chartSeriesColors)
@@ -141,7 +145,7 @@ class ChartWidgetActivity : AbstractBaseActivity() {
141145

142146
override fun onCreateOptionsMenu(menu: Menu): Boolean {
143147
Log.d(TAG, "onCreateOptionsMenu()")
144-
loadedChartData?.let { data ->
148+
dataCacheFragment?.loadedData?.let { data ->
145149
menuInflater.inflate(R.menu.chart_menu, menu)
146150
menu.removeItem(R.id.show_legend)
147151

@@ -186,13 +190,12 @@ class ChartWidgetActivity : AbstractBaseActivity() {
186190
is Period -> outState.putSerializable(PERIOD, p)
187191
is Duration -> outState.putSerializable(PERIOD, p)
188192
}
189-
outState.putByteArray(DATA, loadedChartData?.toByteArray()?.compress())
190193
super.onSaveInstanceState(outState)
191194
}
192195

193196
override fun onStart() {
194197
super.onStart()
195-
val data = loadedChartData
198+
val data = dataCacheFragment?.loadedData
196199
val loadExistingData = data?.let {
197200
val dataUsagePolicy = determineDataUsagePolicy(ConnectionFactory.activeUsableConnection?.connection)
198201
val now = Instant.now().atZone(data.timestamp.zone)
@@ -219,7 +222,7 @@ class ChartWidgetActivity : AbstractBaseActivity() {
219222
return@launch
220223
}
221224

222-
loadedChartData = null
225+
dataCacheFragment?.loadedData = null
223226
invalidateOptionsMenu()
224227
showLoadingIndicator()
225228

@@ -252,7 +255,7 @@ class ChartWidgetActivity : AbstractBaseActivity() {
252255
}
253256

254257
configureChartForData(data)
255-
loadedChartData = data
258+
dataCacheFragment?.loadedData = data
256259
invalidateOptionsMenu()
257260
showChart()
258261
}
@@ -678,6 +681,14 @@ class ChartWidgetActivity : AbstractBaseActivity() {
678681

679682
private class StateParsingException(cause: Throwable, val item: Item) : Exception(cause)
680683

684+
@Suppress("DEPRECATION")
685+
class ChartDataCacheFragment : Fragment() {
686+
var loadedData: ChartData? = null
687+
init {
688+
retainInstance = true
689+
}
690+
}
691+
681692
companion object {
682693
private val TAG = ChartWidgetActivity::class.java.simpleName
683694

@@ -700,7 +711,6 @@ class ChartWidgetActivity : AbstractBaseActivity() {
700711
private const val DATA_POINT_LIMIT = 500000
701712

702713
private const val PERIOD = "period"
703-
private const val DATA = "data"
704714
const val EXTRA_WIDGET = "widget"
705715
const val EXTRA_SERVER_FLAGS = "server_flags"
706716
}

mobile/src/main/java/org/openhab/habdroid/util/ExtensionFuncs.kt

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ import android.net.Uri
3636
import android.net.wifi.WifiManager
3737
import android.os.Build
3838
import android.os.Bundle
39-
import android.os.Parcel
40-
import android.os.Parcelable
4139
import android.util.DisplayMetrics
4240
import android.util.Log
4341
import android.util.TypedValue
@@ -60,7 +58,6 @@ import com.google.android.material.color.MaterialColors
6058
import com.google.android.material.shape.MaterialShapeDrawable
6159
import com.google.android.material.shape.RelativeCornerSize
6260
import com.google.android.material.shape.ShapeAppearanceModel
63-
import java.io.ByteArrayOutputStream
6461
import java.io.EOFException
6562
import java.io.IOException
6663
import java.io.InputStream
@@ -73,9 +70,6 @@ import java.security.cert.CertPathValidatorException
7370
import java.security.cert.CertificateExpiredException
7471
import java.security.cert.CertificateNotYetValidException
7572
import java.security.cert.CertificateRevokedException
76-
import java.util.zip.Deflater
77-
import java.util.zip.DeflaterOutputStream
78-
import java.util.zip.InflaterInputStream
7973
import javax.jmdns.ServiceInfo
8074
import javax.net.ssl.SSLException
8175
import javax.net.ssl.SSLHandshakeException
@@ -724,34 +718,3 @@ inline fun <reified T : Serializable> Bundle.serializable(key: String): T? = whe
724718
@Suppress("DEPRECATION")
725719
getSerializable(key) as? T
726720
}
727-
728-
fun ByteArray.compress(): ByteArray {
729-
val baos = ByteArrayOutputStream()
730-
val compressionLevel = 3 // on a 1-9 scale, best compromise for speed without giving up too much on size
731-
DeflaterOutputStream(baos, Deflater(compressionLevel)).use { it.write(this) }
732-
return baos.toByteArray()
733-
}
734-
735-
fun ByteArray.uncompress() = InflaterInputStream(inputStream()).use { it.readBytes() }
736-
737-
fun Parcelable.toByteArray(): ByteArray {
738-
val p = Parcel.obtain()
739-
p.writeParcelable(this, 0)
740-
val bytes = p.marshall()
741-
p.recycle()
742-
return bytes
743-
}
744-
745-
inline fun <reified T : Parcelable> ByteArray.extractParcelable(): T? {
746-
val p = Parcel.obtain()
747-
p.unmarshall(this, 0, size)
748-
p.setDataPosition(0)
749-
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
750-
p.readParcelable(T::class.java.classLoader, T::class.java)
751-
} else {
752-
@Suppress("DEPRECATION")
753-
p.readParcelable<T>(T::class.java.classLoader)
754-
}
755-
p.recycle()
756-
return result
757-
}

0 commit comments

Comments
 (0)