Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Crafting Seamless UX by Preserving App State in...

Crafting Seamless UX by Preserving App State in Android (By: Adil Soomro) - Google I/O Extended Lahore 2023

Talk by Adil Soomro (https://www.linkedin.com/in/adilsoomro/) at Google I/O Extended Lahore 2023 by GDG Lahore.

GDG Lahore

August 26, 2023
Tweet

More Decks by GDG Lahore

Other Decks in Programming

Transcript

  1. Agenda What is a state and why preserve How app

    lose state How to preserve state Conclusion 01 02 03 04
  2. State concept and data blueprints View vs Jetpack Compose Stateless

    vs. Stateful User expectations and system behavior What is a State and why preserve
  3. // Imperative Style ViewB b = new ViewB(...); b.setColor(red); b.clearChildren();

    ViewC c3 = new ViewC(...); b.add(c3); String text = "Large Heading"; TextView textView = new TextView(...); textView.setText(text); ... text = "Large Heading Changed"; textView.setText(text); // Declarative Style return ViewB( color = Color.RED ) { ViewC() } var text = mutableStateOf("Android") ... Text( text = text, ), ... text = "Large Heading"; ...
  4. Theme changes, Device orientation changes App resizes System running low

    on memory Configuration Changes User can swipe away your app App crashes in background App updated in background System need resources Unexpected app dismissal How app lose state
  5. Opting out of some changes Save transient data Rebuilding Save

    transient data for State Configuration Changes Recover from unexpected state Save data to storage System need resources Unexpected app dismissal How to preserve state
  6. ViewModel Preserve State Survives configuration changes In memory Limited by

    available memory Quick reads/writes Navigation caches them if the destination is in the back stack
  7. public class SavedStateViewModel extends ViewModel { private static final String

    NAME_KEY = "name"; private SavedStateHandle mState; public SavedStateViewModel(SavedStateHandle savedStateHandle) { mState = savedStateHandle; } // Expose an immutable LiveData LiveData<String> getName() { // getLiveData obtains observable object for the key, wrapped in a LiveData return mState.getLiveData(NAME_KEY); } void saveNewName(String newName) { // Sets a new value for the object associated to the key mState.set(NAME_KEY, newName); } }
  8. mSavedStateViewModel = new ViewModelProvider(this).get(SavedStateViewModel.class); // Show the ViewModel property's value

    in a TextView mSavedStateViewModel.getName().observe(this, new Observer<String>() { @Override public void onChanged(String savedString) { ((TextView) findViewById(R.id.saved_vm_tv)) .setText(getString(R.string.saved_in_vm, savedString)); } }); // Save button findViewById(R.id.save_bt).setOnClickListener(v -> { String newName = ((EditText)findViewById(R.id.name_et)) .getText().toString(); mSavedStateViewModel.saveNewName(newName); });
  9. Persistent Storage Preserve State Survives configuration changes, system needing resources,

    and unexpected app dismissal On disk Limited by disk space Slow reads/writes Application data LiveData Room
  10. Saved State API Preserve State Survives config changes, and the

    system needing resources In memory Limited by the Bundle Slow reads/writes Large objects or lists
  11. onSaveInstanceState class ChatBubbleView(context: Context, ...) : View(context, ...) { private

    var isExpanded = false override fun onSaveInstanceState() : Parcelable { super.onSaveInstanceState() return bundleOf(IS_EXPANDED to isExpanded) } override fun onestoreInstanceState(state: Parcelable) { isExpanded = (state as Bundle) getBoolean(IS_EXPANDED) super.onRestoreInstanceState(null) } companion object { private const val IS_EXPANDED = "is expanded" } }
  12. @Composable fun ChatBubble(message: Message) { var showDetails by rememberSaveable {

    mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } } rememberSaveable
  13. class ConversationViewModel ( savedStateHandle: SavedStateHandle ): ViewModel() { var message

    by savedStateHandle.saveable( stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } fun send() { /* Send current message to the data layer */ } } SavedStateHandle
  14. Storage location In memory In memory Disk/Network Survives Config Changes

    Yes Yes Yes Survives system initiated death No Yes Yes Survives user complete activity dismiss/finish No No Yes Data limitation Space is limited ~ 50KB, primitive data Available disk space / cost Read write time Quick Slow (serialization) Slow (Disk / Network IO) ViewModel Save API Persistent Storage How to preserve state
  15. Best Practices Preserve State Avoid static object references Avoid large

    object references Use save state approaches Look for edge cases Do run some tests with Test apis