Reporting the Current Screen
Digia.setCurrentScreen tells Digia Engage which screen the user is on. This page is the
implementation guide: where the call goes in each stack, how to wire it to your navigation library,
and how to choose names that actually match your campaigns.
For what screen targeting does and how a campaign owner uses it, see Screen Targeting.
setCurrentScreen is a Digia Engage core SDK capability. It behaves the same across React
Native, Android, iOS, and Flutter, and across every integration — whether a CEP (CleverTap,
MoEngage, WebEngage) drives delivery or Digia runs its own campaigns.
What the screen name drives
It is not only for targeting. One call feeds five things:
| Consumer | What the screen name does |
|---|---|
| Campaign eligibility | A screen-targeted campaign renders only while the current screen is one of its targets. |
| Your CEP's screen view | Forwarded to CleverTap recordScreenView, MoEngage trackScreen, or WebEngage screen. This replaces your own CEP "record screen" call — you do not need both. |
| Analytics | Attached as screen_name to every impression Digia records. |
| Spotlight targeting | Anchorless spotlight steps match on the screen name. |
| Component capture (debug) | Anchors are skipped entirely while no screen is set, so capture comes back empty. |
On Android and iOS the screen name also feeds screen-based frequency capping.
Automatic or manual?
There are two ways to report screens, and they are not equivalent.
| Automatic (wire a navigator once) | Manual (call per screen) | |
|---|---|---|
| Effort | One-time setup | One line per screen |
| Name you get | Whatever your router calls the route | Whatever you choose |
| Covers tab switches | No | Yes |
| Covers unnamed routes | No | Yes |
| Available on | Flutter, iOS | All stacks |
Recommendation: manual, with a central constants file. It is one line per screen, it gives you names that match your dashboard, and it handles the cases a router cannot see. Reach for the automatic path when your app is large and already uses named routes that are exactly the strings your campaign owners type.
What ships today:
| Stack | Automatic helper |
|---|---|
| Flutter | DigiaNavigatorObserver |
| iOS | DigiaNavigationScreenObserver (opt-in UINavigationControllerDelegate) |
| Android | None — call manually |
| React Native | None — call manually |
Where the manual call goes
The subtle part is picking a lifecycle hook that fires on every arrival — not just the first mount. A screen the user navigates back to, or a tab they re-select, is still an arrival.
- React Native
- Flutter
- Android
- iOS (Swift)
Use useFocusEffect, not useEffect. A tab screen stays mounted when you navigate away, so
useEffect fires once and never again:
import { useFocusEffect } from '@react-navigation/native'; // or from 'expo-router'
import { useCallback } from 'react';
import { Digia } from '@digia-engage/core';
export function useScreenTracking(screenName: string) {
useFocusEffect(
useCallback(() => {
Digia.setCurrentScreen(screenName);
}, [screenName]),
);
}
Then one line per screen:
export function HomeScreen() {
useScreenTracking(ScreenNames.home);
return /* … */;
}
Not using hooks? Digia is a plain singleton, so Digia.setCurrentScreen(...) works from
componentDidMount, a navigation listener, a saga, or any non-React module. The hook is convenience,
never the only path.
initState fires once per mount and not when the user navigates back — the screen underneath
stays mounted the whole time. So initState alone silently under-reports.
Either let DigiaNavigatorObserver handle back-navigation (it reports the revealed route on pop), or
report explicitly at each entry point:
class CheckoutScreen extends StatefulWidget { /* … */ }
class _CheckoutScreenState extends State<CheckoutScreen> {
void initState() {
super.initState();
Digia.setCurrentScreen(ScreenIds.checkout);
}
}
For tabs and other cases the observer cannot see, see section 5.
Use onResume(), not onCreate(), so the call repeats when the user returns:
import com.digia.engage.digiaScreen
class CheckoutActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
digiaScreen(ScreenIds.CHECKOUT)
}
}
digiaScreen(...) is available on both Activity and Fragment.
In Compose, use the DigiaScreen composable:
import com.digia.engage.DigiaScreen
@Composable
fun CheckoutScreen() {
DigiaScreen(ScreenIds.CHECKOUT)
// … your screen
}
DigiaScreen reports when the composable enters composition or its name changes. Inside a
HorizontalPager that keeps neighbouring pages composed, swiping between pages will not re-report
— drive it from the pager's selected index instead.
Use viewDidAppear, not viewDidLoad:
import DigiaEngage
final class CheckoutViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
digiaScreen(ScreenIds.checkout)
}
}
In SwiftUI, call from .onAppear at the screen's root view only:
struct CheckoutScreen: View {
var body: some View {
ScrollView { /* … */ }
.onAppear { Digia.setCurrentScreen(ScreenIds.checkout) }
}
}
Inside a List or LazyVStack, .onAppear fires per row as cells scroll into view. Attach it to the
screen's root view, never to a row.
Set the screen before your triggers fire
This is the most common cause of a campaign that "just doesn't appear".
- A campaign dropped for the wrong screen is not retried. If a campaign triggers while the current screen doesn't match, it is dropped there and then. It does not re-appear when the user later arrives on the targeted screen — your CEP has to trigger it again.
- The screen is never remembered across launches. After every cold start it is unset, and while it is unset every screen-targeted campaign is dropped.
So report the first screen as early as you can — right after Digia.initialize(...), or from your
first screen's arrival hook — so a launch-triggered campaign has a screen to match against.
Re-report when the app returns to the foreground
When the user backgrounds your app and comes back, they are on the same screen but your CEP may need the in-app re-requested. The SDK does not do this for you. Re-report the last screen on foreground:
import { AppState } from 'react-native';
let currentScreen = ScreenNames.home;
AppState.addEventListener('change', (state) => {
if (state === 'active') Digia.setCurrentScreen(currentScreen);
});
The same pattern applies on other stacks (scenePhase on iOS, ON_START on Android,
AppLifecycleListener on Flutter). Repeating the same screen name is safe.
React Native: a screen reported before Digia.initialize(...) finishes is not applied to
screen-triggered campaigns. Report your first screen after initialization resolves.
Flutter with your router
- GoRouter
- Plain Navigator
- AutoRoute / other
- Tab shells
DigiaNavigatorObserver works with GoRouter — pass it in observers:
final router = GoRouter(
observers: [DigiaNavigatorObserver()],
routes: [
GoRoute(name: 'checkout', path: '/checkout', builder: (_, __) => const CheckoutScreen()),
],
);
What name lands depends on how you declared the route:
- Named route (
GoRoute(name: 'checkout', …)) → reportscheckout. Naming your routes gives you clean dashboard names for free — this is the recommended setup. - Unnamed route → reports the path pattern, for example
/product/:id, not the resolved/product/42. That is stable across products (good for targeting) but is not a string a campaign owner would naturally type, so align the dashboard to it or name the route.
Two things that silently break this:
- A custom
pageBuilderreturningCustomTransitionPagemust passname:itself, otherwise that route reports nothing. GoRouter,ShellRoute, andStatefulShellBrancheach take their ownobserverslist, and a single observer instance can only be attached to oneNavigator. Nested navigators need their ownDigiaNavigatorObserver()instance each.
Add the observer to MaterialApp:
MaterialApp(
navigatorObservers: [DigiaNavigatorObserver()],
builder: (context, child) => DigiaHost(child: child!),
);
It reports RouteSettings.name on push, replace, and back-navigation. Routes without a name report
nothing — including showDialog and showModalBottomSheet, which is intentional: a dialog is not a
screen change, and the screen stays whatever it was underneath.
If your routes are unnamed, or you want dashboard-friendly names instead of /checkout, call
Digia.setCurrentScreen manually instead.
Any router that renders through a Navigator accepts a NavigatorObserver, so
DigiaNavigatorObserver can be attached — but the name it sees is whatever that router puts in
RouteSettings.name, which varies.
If the reported names don't match your dashboard, report manually from each screen instead (section 3). That is always available and always exact.
A tab switch is a screen change with no route push, so no observer can see it. If your app has a
bottom navigation bar over an IndexedStack, you must report tab changes yourself:
class _MainShellState extends State<MainShell> {
static const _tabScreenIds = [
ScreenIds.home,
ScreenIds.brands,
ScreenIds.account,
ScreenIds.cart,
];
void initState() {
super.initState();
Digia.setCurrentScreen(_tabScreenIds[currentIndex]);
}
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(index: currentIndex, children: _screens),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
Digia.setCurrentScreen(_tabScreenIds[index]);
// … your own tab handling
},
),
);
}
}
Report in both initState (for the tab the app opens on) and onTap.
React Native with your navigator
- React Navigation
- Expo Router
- react-native-navigation
- No navigation library
Per-screen with useFocusEffect is the recommended setup — see
section 3.
If you cannot touch every screen, a container-level listener is a fallback:
<NavigationContainer
ref={navigationRef}
onStateChange={() => {
const routeName = navigationRef.getCurrentRoute()?.name;
if (routeName) Digia.setCurrentScreen(routeName);
}}
>
This fires on any navigation state change, including route parameter updates, so the same screen
is reported repeatedly. It also reports the route's name prop, which is often not the name your
campaign owner would use. Prefer the per-screen hook.
Identical to React Navigation — import useFocusEffect from expo-router instead:
import { useFocusEffect } from 'expo-router';
Everything else, including the hook in section 3, is unchanged.
Wix's react-native-navigation has no React focus hook. Use its own lifecycle listener, registered
once at startup:
import { Navigation } from 'react-native-navigation';
import { Digia } from '@digia-engage/core';
Navigation.events().registerComponentDidAppearListener(({ componentName }) => {
Digia.setCurrentScreen(SCREEN_NAMES[componentName] ?? componentName);
});
Map component names to your dashboard names rather than reporting the raw component name.
Plenty of React Native apps swap screens with state rather than a router. Report the screen wherever you make that switch:
function App() {
const [screen, setScreen] = useState(ScreenNames.home);
useEffect(() => {
Digia.setCurrentScreen(screen);
}, [screen]);
return screen === ScreenNames.home ? <Home onNext={setScreen} /> : <Details />;
}
Because there is no navigator, this single effect is complete — it fires on every switch, forward or back.
Choosing screen names
The name you report is matched exactly and case-sensitively against the campaign's target
screens. Checkout, checkout, and /checkout are three different screens, and a mismatch fails
silently.
Use semantic names from a shared constants file — not route paths. Every automatic source hands you a string a campaign owner would not naturally type:
| Source | What you get |
|---|---|
| GoRouter (unnamed route) | /product/:id |
| Navigation-Compose | product/{id} |
| iOS navigation observer | HomeViewController |
| React Navigation | whatever the route's name prop is |
One constants file per app keeps developers and campaign owners on the same vocabulary:
- React Native
- Flutter
export const ScreenNames = {
home: 'home',
cart: 'cart',
productDetail: 'product_detail',
} as const;
class ScreenIds {
ScreenIds._();
static const home = 'home';
static const cart = 'cart';
static const productDetail = 'product_detail';
}
Conventions that avoid trouble: lowercase snake_case, no leading slash, and never a trailing
space — a stray space in a dashboard screen name matches on some platforms and not others.
Agree the vocabulary with whoever builds campaigns, and use those exact strings on both sides. A mismatch produces no error — the campaign is simply dropped, with only a debug log.
Verify it's working
With verbose logging enabled, the SDK logs every screen you report — something like
Current screen set: checkout. If you never see it, your call isn't running; if you see it with the
wrong value, your naming is the problem.
When a campaign is dropped for the screen, the SDK logs both sides of the comparison — the current
screen (shown as <unset> if you never reported one) and the campaign's target screens. That log
line tells you immediately whether the fix is to report a screen, rename it, or change the campaign.
For an end-to-end check, use Live Testing to fire a screen-targeted campaign at your device, and SDK Debug Settings to inspect SDK state on-device.
Use it correctly
- Report every screen the user lands on, as early as possible and on every arrival — including the first screen after launch.
- Use a lifecycle hook that repeats:
useFocusEffect,onResume,viewDidAppear— notuseEffect,onCreate, orviewDidLoad. - Report tab switches yourself. No navigator observer can see them.
- Keep names identical to the dashboard (exact, case-sensitive). This is the single most common reason a screen-targeted campaign doesn't appear.
- Set the screen before campaigns trigger. A campaign dropped for the wrong screen is never retried.
- Delete your own CEP screen-view call.
setCurrentScreenalready forwards it.
Related pages
- Screen Targeting — what screen targeting does and which campaigns honor it
- Core Concepts
- User Identity
- Digia as Your CEP