Go back

Why Declarative UI Uses Explicit Lifecycle Subscriptions Instead of onResume

Published:  at  08:01 AM
⏱️ 909 words • 5 min read

阅读中文版

Why declarative UI frameworks use explicit lifecycle subscriptions instead of component-level onResume, with examples from Flutter and Compose.

The disappearing “entity”: from View to Composable/Widget

In the world of nativeAndroid development, after creating a template project, you will get a MainActivity and activity_main.xml. We are used to binding Button and Text in MainActivity and then setting various properties. They are all Views that can see and touch. But in Flutter, What I got was the main() function and a bunch of StatelessWidget and StatefulWidget. Not only Flutter, but also Jetpack Compose is also moving closer to this kind of ‘Declarative UI’. OnResume is also not found in Compose (except for the entry Activity), instead it is the nested Composable function

The essence of this shift is: we no longer hold a “handle” to the UI, we only hold the “data”.

Precisely because we only hold data, and the data itself has no concept of ‘front and back’ (how does a String know whether it is in the foreground?). Therefore, looking for onResume in declarative UI is a false proposition in itself. Only when the data needs to be refreshed based on the system status (such as the user coming back), do we actively query the system.

Life cycle evolution diagram

onResume no longer belongs to the UI component

”Reshaping Constraints” of View System

In native Android, Activity is a heavy container that directly occupies the system’s window resources and input focus. This design determines that developers must have a strong “survival awareness”:

  • Foreground and background switching: We must master onPause and onResume precisely to stop the animation or pause the video when focus is lost.
  • Extreme Survival: Under the extreme test of the battery optimization strategy or “No activity retention”, the Activity will be destroyed at any time, and we have to rely on onSaveInstanceState to maintain the state with difficulty.
  • Task flow: Time-consuming background tasks must be moved to a dedicated Foreground/Background Service, otherwise they will be “sacrifice” by the system at any time.

”Logical Refactoring” for Declarative UI

But in the world of Flutter and Compose, this “resource anxiety” is blocked. We are dealing with lightweight functions and configurations.

  • UI is snapshot: The interface is no longer a long-lasting entity, but an instantaneous expression of data. Destroying a Widget is like throwing away a piece of scratch paper, and the cost is extremely low.
  • Underlying reconstruction: This change is not only grammatical, but also a reconstruction of the underlying survival logic. You no longer need to keep an eye on the life and death of each View object, you only need to protect the State behind it.

The nature of disappearance: Why do they dare to “kill” onResume?

In native Android, View is an object with Stable Identity. The memory address of the Button you get through findViewById does not change before it is destroyed, so it can safely hold state and life cycle callbacks.

In Flutter, Widget is just an immutable configuration description (Immutable Configuration). It may be recreated every frame. You cannot bind a lifecycle to a “snapshot” because the object itself can disappear at any time. What really has a life cycle is not the Widget, but the underlying State object, because it remains stable when the Widget is rebuilt.

Only “subscribe” to the environment when the business needs it

In fact, Flutter is not without a life cycle, but it is no longer a built-in capability of UI components. We need to distinguish three dimensions of life cycle:

  • App level (WidgetsBinding): front and back signals of the entire application process.
  • Page level (RouteAware): Whether the current page is visible at the top of the navigation stack.
  • Component level (Composition): The process of a Widget entering or exiting the UI tree.

Completely decoupling these signals from UI description is what makes Flutter extremely flexible.

Flutter solution: monitoring AppLifecycleState

In Flutter, you need to use WidgetsBindingObserver to play the role of “sentinel”.

The subscription of WidgetsBindingObserver is at the State level, not at the Widget level.

// 建议替换原本的 Flutter 代码块
class MyState extends State<MyWidget> with WidgetsBindingObserver {

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // 严谨判断:只有 App 回到前台,且当前页面正处于栈顶可见时,才触发逻辑
    if (state == AppLifecycleState.resumed && ModalRoute.of(context)?.isCurrent == true) {
      _refreshData();
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

Compose solution: Explicitly access the host life cycle through side effects

Compose doesn’t “remove” onResume, but refuses to let UI components hold it implicitly.

@Composable
fun OnResumeEffect(onResume: () -> Unit) {
    val lifecycleOwner = LocalLifecycleOwner.current
    LaunchedEffect(lifecycleOwner) {
        lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
            onResume()
        }
    }
}

Conclusion: From “passive acceptance” to “active subscription”

When moving from native Android to Flutter or Compose, the hardest part isn’t learning new syntax. It’s changing how you think about who controls the UI.

In the traditional View system, the life cycle is a “family bucket” forced by the system to us. As developers, we are more passively accepting the scheduling of Activity. The emergence of declarative UI has completely liberated components from heavy system environment dependencies.

This “death” is actually an evolution:

  • It makes us pay more attention to the data itself: UI is no longer a long-lasting “house”, but a “snapshot” that flows with the data.

  • It gives us the freedom to subscribe: No longer letting 100% of the components bear life cycle callbacks for the 1% of business needs, but according to the business logic, accurately pulling out a “signal line” where needed.

The life cycle has not disappeared, it has just evolved from “implicit callback” to “explicit subscription”.


Share this post on:

Previous Post
Adding a Xiaomi Yeelight Monitor Light Bar to HomeKit with an ESP32-C3 Controller
Next Post
Mapping and Modifying the Lenovo R720 Keyboard Matrix