Skip to content

feat(background): cross-platform background tasks (iOS + Android)#29

Open
A-Legg wants to merge 11 commits into
GenericJam:masterfrom
A-Legg:feat/background-tasks-phase-3
Open

feat(background): cross-platform background tasks (iOS + Android)#29
A-Legg wants to merge 11 commits into
GenericJam:masterfrom
A-Legg:feat/background-tasks-phase-3

Conversation

@A-Legg
Copy link
Copy Markdown

@A-Legg A-Legg commented May 25, 2026

Summary

Implements Phase 1–3 of the cross-platform background task system for Mob:

  • Phase 1 (iOS): AppDelegate silent push + background fetch hooks via application(_:didReceiveRemoteNotification:) and application(_:performFetchWithCompletionHandler:)mob_begin_background_task.
  • Phase 2 (Android): WorkManager MobBackgroundWorker triggered by FCM data messages (mob_background_task key) → JNI mob_begin_background_task.
  • Phase 3 (Elixir): Convenience API Mob.Background.Task.run_and_complete/1, new_data/0, no_data/0, failed/0.

Changes

  • docs/designs/background_tasks.md — Phase 1 design doc
  • docs/designs/background_tasks_phase2.md — Phase 2 design doc
  • docs/designs/background_tasks_phase3.md — Phase 3 design doc
  • lib/mob/background.ex, lib/mob/background/task.ex — Elixir public API
  • lib/mob/device.ex — iOS silent push / Android FCM device-side wiring
  • src/mob_nif.erl — NIF exports (background_task_complete/2, background_task_current/0)
  • ios/mob_nif.mnif_background_task_complete / nif_background_task_current with g_current_bg_task_id
  • android/jni/mob_nif.zigmob_begin_background_task export + NIF stubs
  • android/jni/mob_beam.hmob_begin_background_task declaration
  • android/jni/mob_erts.zigenif_monotonic_time / ErlNifTimeUnit
  • Test updates in test/mob/background_test.exs, test/mob/background/task_test.exs

Related PRs

  • mob_new template changes: (see PR link below)

A-Legg and others added 11 commits May 23, 2026 18:40
- Remove 'static' from mob_iap_send* helpers in mob_nif.m so Swift
  @_silgen_name exports resolve correctly at link time.
- Fix ObjC selector generation: restorePurchases(_:) and
  currentEntitlements(_:) instead of restorePurchases(pidBytes:).
- Correct Nif.ex moduledoc: 5 NIFs, not 6 (iap_verify_receipt is
  pure Elixir HTTP, not a NIF).
Native C bridges (iOS mob_nif.m + Android iap.c) were producing nested
2-tuples {{:iap, :products}, json} instead of the 3-tuple
{:iap, :products, json} that all Elixir handle_info clauses expect.

- mob_iap_send_products: enif_make_tuple2(tag, json) →
  enif_make_tuple3(:iap, :products, json)
- mob_iap_send_transaction: enif_make_tuple2(inner, json) →
  enif_make_tuple3(:iap, tag, json)
- Android sendToBeam: same 2→3 tuple fix

This makes the message format consistent between iOS, Android, and
the documented API contract in MobIap.StoreScreen etc.
- mob_erts.zig: add missing enif_alloc/enif_free/enif_get_list_length/
  enif_get_list_cell declarations required by IAP NIF stubs.
- mob_nif.zig IAP stubs: replace std.heap.c_allocator with erts.enif_alloc
  to avoid libc dependency in build-obj mode; fix buf size (256→4096);
  fix pointer/slice/alignment casts for Zig 0.16; remove pointless
  _ = env discard.
- iap.c: replace free(p) with enif_free(p) so allocation/free pair
  both go through the BEAM allocator.

These fix the Android native build so mob_iap_init is compiled and
linked into lib<app>.so (verified on emulator).
The Zig NIF stubs allocate pid_ptr and product-id strings via
erts.enif_alloc(), but the C JNI wrappers in iap.c were freeing them
with libc free(). This allocator mismatch caused Scudo heap corruption
on Android, crashing the BEAM during distribution or when IAP NIFs were called.

Files changed:
- iap.c: 10 calls to free() -> enif_free() in the JNI wrappers and
  iap_jni_build_string_list
The plugin code (Elixir + native bridges + tests + manifest) now lives
in its own repository at https://github.com/A-Legg/mob_iap.

This commit removes the embedded plugins/mob_iap/ directory from the mob
repo. The runtime NIF stubs in src/mob_nif.erl and the Zig/ObjC
wrappers in android/jni/ and ios/ remain in mob — those are the host
runtime hooks that any IAP plugin calls via :mob_nif.

Changes:
- Delete plugins/mob_iap/ (moved to A-Legg/mob_iap)
- Update guides/mob_dev_mob_new_iap_patches.md to reference the new repo
The plugin code (Elixir + native bridges + tests + manifest) now lives
in its own repository at https://github.com/A-Legg/mob_iap.

This commit removes the embedded plugins/mob_iap/ directory from the mob
repo. The runtime NIF stubs in src/mob_nif.erl and the Zig/ObjC
wrappers in android/jni/ and ios/ remain in mob — those are the host
runtime hooks that any IAP plugin calls via :mob_nif.

Changes:
- Delete plugins/mob_iap/ (moved to A-Legg/mob_iap)
- Update guides/mob_dev_mob_new_iap_patches.md to reference the new repo
- Remove stray empty EOF file accidentally created at repo root
  (heredoc leak from local scripting)

- IAP NIFs: validate argc, return badarg on bad input
  - nif_iap_fetch_products: badarg if iap_extract_product_ids returns 0
    (was: continued with empty array, allocated pid, dispatched
    no-op bridge call)
  - all 5 IAP NIFs: explicit argc check

- Document pid_bytes lifetime contract above the mob_iap_send_* helpers:
  one allocation, one send, one free. Storing pid_bytes after a send or
  calling two helpers with the same pid_bytes is a use-after-free.
  Also document why these symbols are intentionally non-static.

- Auto-format ios/mob_nif.m (pre-existing clang-format violations in
  mob_iap_send_products / mob_iap_send_transaction)

- Delete guides/mob_dev_mob_new_iap_patches.md — it was a design spec
  for the mob_new/mob_dev follow-up PRs, which are now in flight. The
  doc disagrees with the merged direction in places (e.g. recommends
  conditional BILLING permission; mob_new#9 chose unconditional).
  Keeping it would mislead future readers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e 1)

Implements the background task registry and completion bridge for iOS
silent push and background fetch, per docs/designs/background_tasks.md.

Changes:
- ios/mob_nif.m: add mob_begin_background_task() C function and
  nif_background_task_complete/2 NIF with UUID-based completion handler
  registry (NSMutableDictionary + ErlNifMutex)
- ios/mob_beam.h: declare mob_begin_background_task for AppDelegate
- src/mob_nif.erl: add background_task_complete/2 export, -nif, stub
- android/jni/mob_nif.zig: add no-op nif_background_task_complete for
  cross-platform API parity
- lib/mob/background/task.ex: new module — complete(id, result) with
  :new_data | :no_data | :failed
- test/mob/background/task_test.exs: unit + on-device integration tests

Refs: docs/designs/background_tasks.md Phase 1
Adds ergonomic convenience API to Mob.Background.Task:

-  — runs a function inside a supervised Task and
  auto-calls the iOS completion handler with mapped result
- , ,  — shortcuts for the current task
- New NIF  on iOS (reads g_current_bg_task_id)
  and Android (no-op returning :none)

iOS: g_current_bg_task_id stores the most-recently-started task UUID
under the existing g_bg_tasks_mutex. mob_begin_background_task sets it.
nif_background_task_current reads it and returns {:ok, id} or :none.

Android: no-op — FCM data messages don't use completion handlers.

Tests: 8 unit tests (0 failures), 5 on-device integration tests.
All 805 tests pass. mix format + mix credo --strict clean.

Refs: docs/designs/background_tasks_phase3.md
- Export mob_begin_background_task from mob_nif.zig (creates
  {:background_task, uuid, type, payload, deadline_us} tuple and
  sends to device dispatcher).
- Declare mob_begin_background_task in android/jni/mob_beam.h.
- Add enif_monotonic_time and ErlNifTimeUnit constants to mob_erts.zig.
- Add design doc: docs/designs/background_tasks_phase2.md.
@A-Legg
Copy link
Copy Markdown
Author

A-Legg commented May 25, 2026

Template PR

@A-Legg
Copy link
Copy Markdown
Author

A-Legg commented May 25, 2026

Related PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant