liveview-native / liveview-client-swiftui

MIT License
353 stars 31 forks source link

Platform/Version handling #58

Closed bcardarella closed 1 year ago

bcardarella commented 1 year ago

I'm not sure if this would turn into a mess within the template or if it would just really impact performance but I've been thinking about a no-op method within the template for future proofing against server-side and client being out of sync. (i.e. server wants to push templates with newer components but client is not on latest iOS or hasn't updated clent)

<Version swift="A.B.C" client="~> 0.1">
   ...
</Version>

Within the client if there is not support for the version of Swift or the client app version then just don't render what is within the block.

Optionally, this could be extended to platforms:

<AppleTV>
   ...
</AppleTV>

This would make template editing and platform support super easy to add, however it may make the template a complete mess. I'd be OK sticking with our original idea of packing the platform information into the connection negotiation that way you can pattern match on it for mount and other functions. It should be in the state object and provided on each reconnect

supernintendo commented 1 year ago

@bcardarella My proposal would be to pass additional metadata to the client in the same way we pass a _platform key to determine whether a client is :web, :ios, :android etc. We're already using that to support render_native/2 in LiveViews and Live Components for conditionally rendering the correct Heex template based on which platform we're rendering it for. Expanding that to support factors like OS version and subplatform (and in the future, maybe even stuff like display attributes or which device features are enabled by the user) is an easy upgrade we can make to the existing client-server "handshake".

For a native client library like liveview-client-swiftui, all we would need to do is extend connectParams to add those additional parameters here. On the Elixir side, we would update the live_view_native library to accept those additional parameters from the client and include it in the assigns (involves changes to LiveViewNative.LiveSession and a few of the LiveView Native macros).

Then in your template you could just pattern on those parameters, perhaps like so:

hello.ios.heex:

<%= case @native.version do %>
  <% {major, _minor, _patch} when major >= 14 -> %>
    <.live_component id="hello" module={MyApp.Components.V2.Hello} native={@native} />

  <% _ -> %>
    <.live_component id="hello" module={MyApp.Components.V1.Hello} native={@native} />
<% end %>

These changes could be made to both the Elixir libraries and native client libraries without breaking backwards compatibility and aren't too implementation heavy (since it's just piggybacking off a system we're already using). I'd be interested to know if there are any limitations or use cases for why we would want to handle this using custom elements instead though. In my opinion, providing the version, subplatform etc. in the @native assigns has a few benefits over that approach:

  1. It allows the developer to use standard Elixir pattern matching instead of a custom pattern.
  2. That information can be used in other ways from a LiveView or Live Component, for example analytics or error reporting to tools like Sentry.
  3. It strikes a nice balance between allowing the developer to render version specific templates while not being so baked in as to encourage the practice. If you think about Phoenix on the web, you could render browser-specific templates using the "user-agent" header; Phoenix won't get in your way but it also doesn't give you any special tools to make that easier.
shadowfacts commented 1 year ago

Between the two, I think I prefer May's proposal. I think it's more straightforward to implement since doing it with custom elements would require either A) specifically handling those elements on the backend when parsing the template or B) sending the template for every supported platform/version to the client, rather than only the template it needs.

If the version/platform info is also part of the assigns, I think that solves the issue of not requiring completely separate templates for each supported version.

bcardarella commented 1 year ago

@shadowfacts yes, I agree. Does the client currently send along platform information upon connection? I imagine that would be enough for the server to need

shadowfacts commented 1 year ago

It currently sends the platform ("ios") but nothing more specific than that. Sending the user interface idiom (phone/pad/tv/mac) and the OS version should be enough. If an app needs more specific information, it can add it to the connect params as well.

DanielDent commented 1 year ago

I'd really like to see standardized support for sending device state information back to the server from the client. Examples I have in mind: app bundle id, version name, version code, device type, language/locale info, OS version, SDK version, install UUID, etc. Likewise with information about permission state (e.g. asked, unasked, granted, denied) for the various permissions an app might need.

Here are some examples of handling of these types of issues with the NativeScript project:

Sometimes a new capability or a bugfix requires a new build of the mobile app, and being able to serve different application variations based on the client's version/capabilities/state can be extremely helpful/essential.

AZholtkevych commented 1 year ago

Ask Brian

AZholtkevych commented 1 year ago

@supernintendo could you please advise here? Is it implemented?

AZholtkevych commented 1 year ago

@supernintendo could you please advise here? Is it implemented?

supernintendo commented 1 year ago

@AZholtkevych Device-specific information is passed as part of the platform metadata (see my comment here) so this is now possible by just matching on the :os_version parameter on the @native assign.

@DanielDent I like the idea of standardizing the platform metadata and expanding it to those other fields. I think the core ask of @bcardarella's post has already been addressed in implementation but your idea of a more standardized solution is worth considering, so I've broken it out into a separate issue. Thanks!