Skip to content

The app process: launch, run, close ​

One call is one app process, and the entries are its whole life: it starts with a launch, your program runs on it, and it closes the way the app closes. This page says what happens between the tap on the app's icon and the first screen, what keeps running behind it, and how the process ends.

What lives on the process: the session and the phone, the open screens, the telemetry not yet delivered, and the network connections. The handles you hold belong to the process and die with it.

Start: which screen main gets first ​

  • A new install (install) shows the login screen and nothing else. main gets that screen, AndroidAppLaunchLoginScreen. Nothing has been sent that needs a session.
  • A stored user (launch) is started again by the app's cold start. The first screen is the feed (FeedTimelineScreen), or the login screen when the stored value holds no session. Name the screen you expect with screen: and main gets exactly that handle; if the app shows another one, the call rejects with UnexpectedScreenError.
  • A resume (resumeSilently, resumeFromBackground, resumeByReentering) puts a checkpointed process back on its own screen (Ways to start the app).
  • After a login in the same process the app continues into the post-login launch by itself: home.logIn(…) returns the screen the launch leaves on top, which is the feed. After an account-recovery login it can be the location-permission screen instead.

A challenge any of them runs into, over the start or, once main runs, over an effect of the screens, is answered by otherwise (Challenges: answer them once with otherwise) and the interrupted effect runs again. Without a branch for it the call rejects with UnexpectedScreenError.

What the launch sends, and why every process launches ​

A launch sends everything the app sends when it starts, in order and in the windows the app uses: the first requests of the session, the session's navigation, the feed's first page. Your program gets the feed's handle as soon as nothing pending can change the screen on top.

Every entry that starts a process runs the launch (launch for a stored session, install and logIn for a new one), and there is no way to act on a feed that was not launched. The reason is the account's safety: hitting feature endpoints from a restored session that has not been used for hours or days, with no launch traffic before them, is a strong anti-abuse signal, because the real app always sends its launch traffic before the person sees the home feed. A resumeSilently sends nothing on purpose, because the process it continues was launched already.

Device readings (battery, charging, camera permission) come from the virtual phone; you set nothing.

Push notifications are registered by the device itself. An Android device makes its own push key the first time it registers and keeps it in the stored value: it registers its token after every login and, at a start whose last registration is more than five hours old, again. You pass nothing for it.

The start-up's background requests ​

The app shows the feed and keeps sending the rest of its start-up in the background. The Simulator does the same, and nothing in it is yours to call. These are the start-up's background requests.

  • They are waited for. When main ends, the process waits for them before it closes, so a program that returns right after the launch still sends the whole launch. checkpoint() and ExportSnapshot wait for them before they read, so what they return holds everything the launch wrote.
  • They can end your call. If Instagram refuses one of them in a way that ends the session (a lost login, a suspended account), main stops with that same typed error. A main that already returned does not hide it: the call rejects with it after the rest went out. Any other failure of one of these requests is only that request's outcome, as in the app.
  • A challenge over them is answered by your otherwise between your program's actions, never beside a running one. What the branches cannot settle ends the process: a running main stops and the call rejects with UnexpectedScreenError.
  • Only the first screen's own request is fatal during the launch. A rejected early request (a 429, a 400 or a 5xx) leaves the launch running, as in the app. A launch that fails before its first screen rolls back and can be retried.
  • If main failed, its own error is the call's, unchanged, and the background failure is logged beside it.

Close ​

When the program ends, by returning, by failing or by being interrupted, the process closes the way the app does, in this order:

  1. The start-up's background requests are waited for.
  2. The telemetry still pending is delivered. The app reports what its user does to Instagram, and the Simulator keeps those reports until the process closes. They belong to the process that made them, so a checkpoint carries none and a resumed process never resends them. A delivery that fails is logged and never replaces your program's result. To send them earlier, inside the program, use FlushTelemetry (Telemetry).
  3. The network connections close.
  4. The process is gone and its handles are dead.

Each step is its own attempt: one that throws is logged and the next still runs.

Aborting the signal is the app being killed. The waits are skipped (or cut short if running) and the connections close at once, so the rejection never waits on the network.

Background and foreground ​

The app's session counters change when it really goes to the background and comes back. A program that simulates those moments marks them with EnterAppBackground and EnterAppForeground, inside main. resumeFromBackground marks the two moments of its own return for you.

ts
import { AndroidIgpapi, EnterAppBackground, EnterAppForeground, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile } from 'node:fs/promises';

const saved = JSON.parse(await readFile('session.json', 'utf8'));

await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      // The person puts the app in the background, does something else, and comes back.
      yield* EnterAppBackground;
      yield* Effect.sleep('2 minutes');
      yield* EnterAppForeground;
      return feed.items.length;
    }),
});

With the app layer ​

The same wait happens when you provide the AndroidApp layer yourself (Compose the app layer yourself): the layer's teardown waits for the start-up's background requests before the connections close, so a program that ends right after a launch still sends the whole launch. The layer cannot change your program's result, so a session-fatal answer reaches you another way: every later call of your program that reaches the network fails with that same typed error, and awaitMobileProcessBackground fails with it if you wait for the requests yourself.

The pages of this site are the README files of the repository. The API reference is generated from the source.