DEV Community

Cover image for Two screens need the same warning. Where should the state live?
Samya for Kordelis

Posted on Edited on

Two screens need the same warning. Where should the state live?

Imagine an Android app with two screens: an order history screen and an account screen. Both need to warn the user when an earlier order needs attention. From either screen, the user can open a dialog, dismiss it or start a recovery action.

The first version might put the logic in the order history ViewModel. Then the account screen needs it too.

Sharing the recovery use case helps. Both ViewModels can call the same function. But each screen may still need to decide when to show the warning, hold the dialog state and handle the user’s actions. That’s where the duplication starts to feel less trivial.

One option is to extract that behaviour into a small presentation component that each ViewModel uses:

interface OrderWarning {
    val state: StateFlow<OrderWarningState>

    fun check()
    fun dismiss()
    fun recover()
}
Enter fullscreen mode Exit fullscreen mode

The component could use the shared domain logic while also managing the state needed to show the warning. Each screen can then decide how to expose that state to its UI.

I like this approach when the behaviour is the same in both places. The ViewModels no longer need to repeat every detail of checking, dismissing and recovering. It also gives the warning behaviour a smaller surface to test.

But extracting it doesn’t answer every question.

What does “dismiss” mean?

Suppose the user dismisses the warning on the order history screen and then opens the account screen.

Should it appear again?

If each screen has its own warning component, each can hold its own dismissal state. If both screens use the same instance, dismissal can affect both. Either could be right, depending on the experience the app is trying to provide.

I would separate that decision from the order’s actual status. Dismissing a dialog shouldn’t quietly mark the underlying order as recovered. One is a presentation choice; the other changes what the app knows about the order.

There is a similar question around recovery. If both screens can start it, the shared domain or data layer needs an appropriate way to handle repeated or overlapping requests. A shared UI component alone doesn’t guarantee that the operation runs only once.

What about a single screen state?

A ViewModel may already expose a StateFlow<ScreenState>. Adding a separate OrderWarning.state gives the UI another state source to observe.

That isn’t automatically a problem. A small, self-contained warning can be easier to understand on its own. But if the screen already has a clear convention of exposing one state, combining the warning into that state may make the screen easier to follow.

For example, the screen state could contain an OrderWarningState, while screen events are forwarded to the warning component. There will still be some wiring in each ViewModel, but it can be a reasonable amount of repetition if it keeps the screen’s public interface clear.

The other direction is to share a ViewModel at a level above both screens. That can make sense if the warning truly belongs to that shared part of the navigation flow. I’d be more cautious if the only reason for sharing the ViewModel is to avoid a few repeated lines: its scope and lifetime then become part of the decision too.

What I’d look at first

Before choosing a pattern, I’d write down the expected behaviour for a few cases:

  • Dismiss the warning on one screen, then open the other.
  • Start recovery and leave the screen before it finishes.
  • Trigger recovery twice.
  • Reopen the app while the order still needs attention.

Those cases help distinguish the state that belongs to the screen from the order status that should be shared. They also make a useful starting point for tests.

I wouldn’t extract a component just because two ViewModels call the same use case. But when they repeat the same presentation behaviour around that call, a small reusable component can be worth trying. I’d keep it if it makes the screens and their tests easier to understand—and reconsider it if following the behaviour means chasing through several layers.

Top comments (0)