Skip to content

iOS Devtools is fully functional with Flipper#5

Merged
KVSRoyal merged 34 commits intomobile-devtoolsfrom
ios-devtools
Jan 9, 2026
Merged

iOS Devtools is fully functional with Flipper#5
KVSRoyal merged 34 commits intomobile-devtoolsfrom
ios-devtools

Conversation

@KVSRoyal
Copy link
Copy Markdown
Member

@KVSRoyal KVSRoyal commented Nov 24, 2025

What Changed

Get the iOS Devtools to open and connect to flipper, delivering expected logs and evaluating expressions.

Also:

  • Add an ios demo so there's a demo that lives in this same repo.
  • Remove the folders with code we're not actually using, since it's messing up the tests.

Why

We want mobile Devtools to make life easier for users of player.

Definition of Done

  • Unit tests
  • Mock features
  • Documentation -- could use more docs still on usage

@KVSRoyal KVSRoyal changed the base branch from main to mobile-devtools November 24, 2025 23:15
@KVSRoyal KVSRoyal changed the title Ios devtools iOS Devtools is fully functional with Flipper Dec 10, 2025
draft.messages.push(message);
});
case "PLAYER_DEVTOOLS_PLUGIN_INTERACTION":
console.log("[REDUCER] PLAYER_DEVTOOLS_PLUGIN_INTERACTION received:", transaction);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: remove in next commit

Comment on lines +150 to +151
player.logger.error(this.name, "Error setting the following log: ", this.logs);
// Silently fail
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: undo in next commit

Comment on lines +287 to +288
this.logger?.deref()?.error(this.name, "Error parsing new flow", e);
console.error("Error parsing new flow", e);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: change back

@KVSRoyal KVSRoyal requested a review from sugarmanz December 11, 2025 04:06
Comment on lines +43 to +47
public func removeListener(_ listener: @escaping MessageListener) {
print("DEBUG [iOS DevtoolsFlipperPlugin]: removeListener() called")
/* TODO: we can't compare listeners directly on ios.
Implement workaround. E.g. register listeners by ID? */
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should we do here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to change the add/remove listener to work with subscribe/unsubscribe pattern instead, where adding the listener returns the way to remove it, like we discussed offline. E.g.

let unsubscribe = addListener(listener)

I think that would work across all the platforms, though I'm hesitant to touch the other platform code for that in this PR.

Is removing the listener need for the MVP? If so, I can change things in this PR. If not, I'd like to break out the change into a separate ticket where I can focus on that one change across all platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is removing the listener need for the MVP?

Kinda depends on what happens — if it doesn't break anything, then it's probably not needed. It doesn't look like we have a good way to invoke this anyways, as long as we aren't invoking stale listeners and crashing the app.

I think that would work across all the platforms, though I'm hesitant to touch the other platform code for that in this PR.

That said, this is just the Flipper plugin connection for iOS — as there isn't platform agnostic code here, you should be able to change the API to match what iOS needs w/o having to worry about other platforms. Android would be applicable from a consistency standpoint, but would still be in parity either way.

Comment on lines +31 to +43
let options = MessengerOptions(
id: playerID,
jsContext: jsContext,
context: .player,
logger: PlayerLogger(logger: player.logger),
sendMessage: flipperPlugin.sendMessage(_:),
addListener: flipperPlugin.addListener(_:),
removeListener: flipperPlugin.removeListener(_:),
messageCallback: try store.dispatch(event:)
)
let messenger = try Messenger(options: options)
self.messenger = messenger
_ = registerMessenger(messenger: messenger)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should come from the base implementation to remove the overhead from folks creating their own devtools plugins.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Was having a hard time doing this initially but figured it out. 👍🏽

Comment on lines +48 to +51
// player.hooks?.state.tap { state in
// // TODO: what is happening here on android?
// let temp: BaseFlowState = .init(status: .completed)
// }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're hooking into teardown logic to deregister Player instance from the FlipperClient. We should also be sending a disconnect event to the actual client to let them know the Player instance is gone.

Copy link
Copy Markdown
Member Author

@KVSRoyal KVSRoyal Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if I can figure out how to do something similar on iOS. 🤔

It's my understanding so far that ReleasedState on android means "the player is going away and being garbage collected" (docs that I'm referencing). iOS doesn't have a counterpart for that, afaict. We only have notStarted, inProgress, completed, and error (link). Are you aware of any ways ios has matched this android pattern in the past? I'm currently exploring deinit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yikes, apparently deinits in Swift aren't actually called when the app terminates. That's fun for me 😆 .

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the React implementation, they hook into component unmount:

return () => {
unsubscribe();
messenger.destroy();
}

On Android, we're effectively hooking into viewmodel teardown via released state. I'm not sure you'd have a hook exposed unless one existed for iOS, but there should be a way to hook into unmount I'd expect.

Comment on lines +118 to +120
print("[MessengerOptions] Converting to JSValue")
print("[MessengerOptions] context:", context)
print("[MessengerOptions] context.rawValue:", context.rawValue)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be removed?

Comment on lines +85 to +93
/// Errors that can occur when trying to load a JS class that will be referenced by a Swift wrapper
enum JSBaseError: Error {
case noSuchFile
case failedToParseScript
case failedToMakeContext
case noSuchJSModule
case noSuchJSClass
case couldNotInstantiateClass
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enum something we should be supplying from core Player (or JS loading) lib to encapsulate possible JS errors?

Copy link
Copy Markdown
Member Author

@KVSRoyal KVSRoyal Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to supply it from a JS loading library that lives in the player-ui repo. I think these would also be helpful errors for us to propagate when loading JS files for JSBasePlugins, since errors are pretty opaque when something goes wrong.

I put it here for now to limit the number of repos I'd have to touch for the MVP of devtools. I'll make an issue? to move these and drop the link here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.bazelrc Outdated
Comment on lines +31 to +32
# common --ios_simulator_device="iPhone 16 Pro"
# common --ios_simulator_version="18.5"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They shouldn't be commented out, no. If you mean the device/version flags, then yes, we do need them. They allow us to ensure a consistent test environment in CI/CD.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they're just for CI, should we put them in the CI-specific Bazel config?

FWIW — I did need to comment them out to run the demo app locally.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, they could affect your local if you don't run through Xcode and instead do bazel run. It will try to find the matching sim locally and if you don't have one, it'll be unhappy. Although I prefer that to having it pick the first simulator available on your machine, which might be an iPhone 4 running iOS 9 😆 . (I believe that's what Bazel does without something specified.)

I'll move it to the CI-specific place for now though. I think we can deal with the "my infinitely old simulator is not working" if it crops up.

@KVSRoyal KVSRoyal marked this pull request as ready for review January 9, 2026 03:54
@KVSRoyal KVSRoyal merged commit 857df78 into mobile-devtools Jan 9, 2026
7 checks passed
@KVSRoyal KVSRoyal deleted the ios-devtools branch January 9, 2026 03:54
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.

2 participants