Skip to content

Commit 04956ce

Browse files
authored
Clear deprecated API usage (onBackPressed, getParcelable, launchWhen*) + up-button guard fix (#55)
* chore: clear deprecated API usage (onBackPressed, getParcelable, launchWhen*) Tier-1 deprecation cleanup, no behavior change except a bug fix noted below: - onBackPressed() overrides -> OnBackPressedDispatcher.addCallback (ProfileSettings, ConfigEdit, RouteSettings, GroupSettings); AssetsActivity's redundant onBackPressed{finish()} removed (default back already finishes). - onSupportNavigateUp() now routes through onBackPressedDispatcher.onBackPressed() so the action-bar up button honors the unsaved-changes guard too (previously it called finish() directly and could silently discard edits) — fixes a real data-loss path. - intent/bundle getParcelable<T> -> IntentCompat/BundleCompat.getParcelable(..., Class) (ProfileSelectActivity, ConfigurationFragment) — typed, non-deprecated on API 33+. - launchWhenStarted/launchWhenCreated -> lifecycleScope.launch (one-shot jobs: ServiceButton delayed animation, AppListActivity loader); whenStarted -> withStarted (StatsBar). Verified on-device: clean back finishes; back/up with unsaved changes both show the save dialog; no crashes. * review: keep STARTED gate on delayed connecting animation Plain lifecycleScope.launch (replacing launchWhenStarted) could run the delayed progress show() while the activity is stopped. Gate the UI mutation with withStarted to preserve the lifecycle-aware behavior (matches StatsBar).
1 parent c89b57f commit 04956ce

10 files changed

Lines changed: 51 additions & 38 deletions

app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import io.nekohasekai.sagernet.widget.ListListener
3838
import kotlinx.coroutines.Dispatchers
3939
import kotlinx.coroutines.Job
4040
import kotlinx.coroutines.ensureActive
41+
import kotlinx.coroutines.launch
4142
import kotlinx.coroutines.withContext
4243
import kotlin.coroutines.coroutineContext
4344

@@ -174,7 +175,7 @@ class AppListActivity : ThemedActivity() {
174175
@UiThread
175176
private fun loadApps() {
176177
loader?.cancel()
177-
loader = lifecycleScope.launchWhenCreated {
178+
loader = lifecycleScope.launch {
178179
loading.crossFadeFrom(binding.list)
179180
val adapter = binding.list.adapter as AppsAdapter
180181
withContext(Dispatchers.IO) { adapter.reload() }

app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,6 @@ class AssetsActivity : ThemedActivity() {
384384
return true
385385
}
386386

387-
override fun onBackPressed() {
388-
finish()
389-
}
390-
391387
override fun onResume() {
392388
super.onResume()
393389

app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.appcompat.widget.SearchView
2727
import androidx.appcompat.widget.Toolbar
2828
import androidx.core.graphics.ColorUtils
2929
import androidx.core.net.toUri
30+
import androidx.core.os.BundleCompat
3031
import androidx.core.view.isGone
3132
import androidx.core.view.isVisible
3233
import androidx.core.view.size
@@ -1180,7 +1181,7 @@ class ConfigurationFragment @JvmOverloads constructor(
11801181
override fun onViewStateRestored(savedInstanceState: Bundle?) {
11811182
super.onViewStateRestored(savedInstanceState)
11821183

1183-
savedInstanceState?.getParcelable<ProxyGroup>("proxyGroup")?.also {
1184+
savedInstanceState?.let { BundleCompat.getParcelable(it, "proxyGroup", ProxyGroup::class.java) }?.also {
11841185
proxyGroup = it
11851186
onViewCreated(requireView(), null)
11861187
}

app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.view.Menu
1010
import android.view.MenuItem
1111
import android.view.View
1212
import android.widget.Toast
13+
import androidx.activity.addCallback
1314
import androidx.activity.result.contract.ActivityResultContracts
1415
import androidx.annotation.LayoutRes
1516
import androidx.appcompat.app.AlertDialog
@@ -246,6 +247,11 @@ class GroupSettingsActivity(
246247
@SuppressLint("CommitTransaction")
247248
override fun onCreate(savedInstanceState: Bundle?) {
248249
super.onCreate(savedInstanceState)
250+
onBackPressedDispatcher.addCallback(this) {
251+
if (needSave()) {
252+
UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)
253+
} else finish()
254+
}
249255
setSupportActionBar(findViewById(R.id.toolbar))
250256
supportActionBar?.apply {
251257
setTitle(R.string.group_settings)
@@ -330,14 +336,10 @@ class GroupSettingsActivity(
330336

331337
override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)
332338

333-
override fun onBackPressed() {
334-
if (needSave()) {
335-
UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)
336-
} else super.onBackPressed()
337-
}
338-
339339
override fun onSupportNavigateUp(): Boolean {
340-
if (!super.onSupportNavigateUp()) finish()
340+
// Route the action-bar up button through the back dispatcher so it honors the
341+
// unsaved-changes guard instead of finishing directly (avoids data loss).
342+
onBackPressedDispatcher.onBackPressed()
341343
return true
342344
}
343345

app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.nekohasekai.sagernet.ui
22

33
import android.content.Intent
44
import android.os.Bundle
5+
import androidx.core.content.IntentCompat
56
import io.nekohasekai.sagernet.R
67
import io.nekohasekai.sagernet.database.ProxyEntity
78

@@ -16,7 +17,7 @@ class ProfileSelectActivity : ThemedActivity(R.layout.layout_empty),
1617
override fun onCreate(savedInstanceState: Bundle?) {
1718
super.onCreate(savedInstanceState)
1819

19-
val selected = intent.getParcelableExtra<ProxyEntity>(EXTRA_SELECTED)
20+
val selected = IntentCompat.getParcelableExtra(intent, EXTRA_SELECTED, ProxyEntity::class.java)
2021

2122
supportFragmentManager.beginTransaction()
2223
.replace(

app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.Menu
99
import android.view.MenuItem
1010
import android.view.View
1111
import android.widget.Toast
12+
import androidx.activity.addCallback
1213
import androidx.activity.result.component1
1314
import androidx.activity.result.component2
1415
import androidx.activity.result.contract.ActivityResultContracts
@@ -221,6 +222,11 @@ class RouteSettingsActivity(
221222

222223
override fun onCreate(savedInstanceState: Bundle?) {
223224
super.onCreate(savedInstanceState)
225+
onBackPressedDispatcher.addCallback(this) {
226+
if (needSave()) {
227+
UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)
228+
} else finish()
229+
}
224230
setSupportActionBar(findViewById(R.id.toolbar))
225231
supportActionBar?.apply {
226232
setTitle(R.string.cag_route)
@@ -300,14 +306,10 @@ class RouteSettingsActivity(
300306

301307
override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)
302308

303-
override fun onBackPressed() {
304-
if (needSave()) {
305-
UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)
306-
} else super.onBackPressed()
307-
}
308-
309309
override fun onSupportNavigateUp(): Boolean {
310-
if (!super.onSupportNavigateUp()) finish()
310+
// Route the action-bar up button through the back dispatcher so it honors the
311+
// unsaved-changes guard instead of finishing directly (avoids data loss).
312+
onBackPressedDispatcher.onBackPressed()
311313
return true
312314
}
313315

app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.view.Menu
77
import android.view.MenuItem
88
import android.view.ViewGroup.MarginLayoutParams
99
import android.widget.LinearLayout
10+
import androidx.activity.addCallback
1011
import androidx.appcompat.app.AlertDialog
1112
import androidx.core.view.ViewCompat
1213
import androidx.core.view.WindowInsetsCompat
@@ -53,6 +54,10 @@ class ConfigEditActivity : ThemedActivity() {
5354
@SuppressLint("InlinedApi")
5455
override fun onCreate(savedInstanceState: Bundle?) {
5556
super.onCreate(savedInstanceState)
57+
onBackPressedDispatcher.addCallback(this) {
58+
if (dirty) UnsavedChangesDialogFragment().apply { key() }
59+
.show(supportFragmentManager, null) else finish()
60+
}
5661

5762
intent?.extras?.apply {
5863
getString("key")?.let { key = it }
@@ -165,13 +170,10 @@ class ConfigEditActivity : ThemedActivity() {
165170
}
166171
}
167172

168-
override fun onBackPressed() {
169-
if (dirty) UnsavedChangesDialogFragment().apply { key() }
170-
.show(supportFragmentManager, null) else super.onBackPressed()
171-
}
172-
173173
override fun onSupportNavigateUp(): Boolean {
174-
if (!super.onSupportNavigateUp()) finish()
174+
// Route the action-bar up button through the back dispatcher so it honors the
175+
// unsaved-changes guard instead of finishing directly (avoids data loss).
176+
onBackPressedDispatcher.onBackPressed()
175177
return true
176178
}
177179

app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import android.view.View
1212
import android.widget.LinearLayout
1313
import android.widget.ScrollView
1414
import android.widget.Toast
15+
import androidx.activity.addCallback
1516
import androidx.activity.result.component1
1617
import androidx.activity.result.component2
1718
import androidx.activity.result.contract.ActivityResultContracts
@@ -92,6 +93,10 @@ abstract class ProfileSettingsActivity<T : AbstractBean>(
9293

9394
override fun onCreate(savedInstanceState: Bundle?) {
9495
super.onCreate(savedInstanceState)
96+
onBackPressedDispatcher.addCallback(this) {
97+
if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() }
98+
.show(supportFragmentManager, null) else finish()
99+
}
95100
setSupportActionBar(findViewById(R.id.toolbar))
96101
supportActionBar?.apply {
97102
setTitle(R.string.profile_config)
@@ -174,13 +179,10 @@ abstract class ProfileSettingsActivity<T : AbstractBean>(
174179

175180
override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)
176181

177-
override fun onBackPressed() {
178-
if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() }
179-
.show(supportFragmentManager, null) else super.onBackPressed()
180-
}
181-
182182
override fun onSupportNavigateUp(): Boolean {
183-
if (!super.onSupportNavigateUp()) finish()
183+
// Route the action-bar up button through the same back dispatcher so it honors
184+
// the unsaved-changes guard instead of finishing directly (avoids data loss).
185+
onBackPressedDispatcher.onBackPressed()
184186
return true
185187
}
186188

app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.appcompat.widget.TooltipCompat
1212
import androidx.dynamicanimation.animation.DynamicAnimation
1313
import androidx.lifecycle.LifecycleOwner
1414
import androidx.lifecycle.lifecycleScope
15+
import androidx.lifecycle.withStarted
1516
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
1617
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
1718
import com.google.android.material.floatingactionbutton.FloatingActionButton
@@ -21,6 +22,7 @@ import io.nekohasekai.sagernet.bg.BaseService
2122
import io.nekohasekai.sagernet.ktx.getColorAttr
2223
import kotlinx.coroutines.Job
2324
import kotlinx.coroutines.delay
25+
import kotlinx.coroutines.launch
2426
import java.util.*
2527

2628
class ServiceButton @JvmOverloads constructor(
@@ -64,10 +66,14 @@ class ServiceButton @JvmOverloads constructor(
6466
private val iconConnecting by lazy {
6567
AnimatedState(R.drawable.ic_service_connecting) {
6668
hideProgress()
67-
delayedAnimation = (context as LifecycleOwner).lifecycleScope.launchWhenStarted {
69+
delayedAnimation = (context as LifecycleOwner).lifecycleScope.launch {
6870
delay(context.resources.getInteger(android.R.integer.config_mediumAnimTime) + 1000L)
69-
isIndeterminate = true
70-
show()
71+
// Gate the UI mutation on STARTED so a delayed progress reveal doesn't run
72+
// while the activity is stopped (the old launchWhenStarted suspended here).
73+
(context as LifecycleOwner).withStarted {
74+
isIndeterminate = true
75+
show()
76+
}
7177
}
7278
}
7379
}

app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import android.widget.TextView
1111
import androidx.appcompat.widget.TooltipCompat
1212
import androidx.coordinatorlayout.widget.CoordinatorLayout
1313
import androidx.lifecycle.lifecycleScope
14-
import androidx.lifecycle.whenStarted
14+
import androidx.lifecycle.withStarted
1515
import com.google.android.material.bottomappbar.BottomAppBar
1616
import io.nekohasekai.sagernet.R
1717
import io.nekohasekai.sagernet.bg.BaseService
@@ -125,7 +125,7 @@ class StatsBar @JvmOverloads constructor(
125125
val activity = context as MainActivity
126126
fun postWhenStarted(what: () -> Unit) = activity.lifecycleScope.launch(Dispatchers.Main) {
127127
delay(100L)
128-
activity.whenStarted { what() }
128+
activity.withStarted { what() }
129129
}
130130
if ((state == BaseService.State.Connected).also { hideOnScroll = it }) {
131131
postWhenStarted {

0 commit comments

Comments
 (0)