Modeling continuation of action after sign in the composable architecture

I have an app that I’m considering moving to TCA.

The app can be used unauthenticated, but certain actions require an account.

I currently have a nice AuthenticatedActionButton which takes a closure that takes a user object. If the user is authenticated when they click the button, the closure is run immediately with that user object. If they are not authenticated, a login sheet is presented, which allows a user to sign up or sign in idempotently, and once that flow is completed, the original closure is run with the newly created or loaded user. There is a single global auth solution, and the caller of the AuthenticatedActionButton doesn’t need to know anything about the auth stuff.

It works through environment objects and a global sign in state, so it feels like it should be portable to TCA.

I understand how to present the sheet globally from anywhere in the app using TCA, and how to have a global common identity, by using dependencies.

However, I’m struggling with how to have the original arbitrary closure take place after the sign in flow is completed. I essentially want to chain an effect after the sign in effect. Running it immediately when the user is already signed in is straightforward. But when the user is not signed in, the state needs to be set to show the sign in sheet. Everything I’ve read says that State should be simple value types so it doesn’t seem right to include the callback/continuation closure on the sign in state.

The button can be used anywhere within the app, at any depth, within a number of features.

How might we model this in TCA?

Hey @majelbstoat !
I would go with some service set as Dependency, and maybe save the authentication result value on user defaults, to access the service and check from every feature domain you need to, then hydrating some optional value from within your feature state and doing some navigation as you want too. Are you aware of the Dependency system lib that fits pretty well with TCA?

If you wanna be greedy, you can also maybe add some AuthenticationFeature that conforms with the Reducer protocol and potentially interacts with your AuthenticationService dependency, then set it as a child feature on each parent feature you want to access/use authentication features.

Hey @majelbstoat this is a very interesting question.

I'd like to take a shot at brainstorming some different ideas around modeling your domain to hopefully at the very least provide father for useful conversation down the line

I'm typing this with my voice from my iPhone on iOS 17, which is definitely more accurate than previous versions but there will certainly be typos especially in the programming domain so I'll try to avoid any complex language

As I was reading your question and came across the fact that you have a UI element that requires the user object that made me feel like your Business logic is tied in with your UI and I believe that to be somewhat of an anti-pattern especially in a library such as the compostable architecture where we are encouraged to represent our logic in terms of state and actions independent of the UI

I'll go into detail at the end if I have time but let me speak about how I would propose a change to modeling your domain with the disclosure that perhaps these might be not the best way to solve the problem but are certainly things to think about

First to immediately address your question Will need to brainstorm what state we need to hold onto and I think you already have this we need to know if the users authenticated or not and we need to know their intended action when they press this button so let's say for simplicity that we want to implement a sign in function before a user can Book a restaurant in our app

And let's also assume that we want to present that book a restaurant button before user has logged in

We want to allow them to experience our application in full, something similar to openable perhaps, but prevent spam by requiring authentication prior to booking

As a a user the application a user story would look like

I download the application
Navigate to my favorite restaurant
I click book
I can optionally tell some information about the reservation is it a date and I taking my parents out to dinner etc.
Just a sidenote here that this information should be saved in case I want to come back and close the application and perhaps not go to the sign and flow this moment
And so that would require that we save a little bit more state something called pending reservations that is saved to the disk

OK now at the inflection point of your original question
I'm still logged out user what do we do
Well we know the user wants to Book a reservation at their favorite restaurant with some certain details so we could call that like a reservation object or something a reservation struct
And there can be one editing reservation struct or something like that some scratch that we saved as they call it in their videos
So in our action that we dispatch from the book this restaurant button we will know that the intent of the user is to Book the restaurant
Do we say that to state as well
So now
If the user is required to go through a login flow in our application we need to present them with some kind of way to do so
I think you mentioned in your original question that we have a model that you present to allow them to do so
That model should be driven by the state
And once you complete that asynchronous login flow
And you have successfully acquired some kind of Digital token identifying the user that your back end will accept
You'll receive an action back into your store that you should send yourself user authenticated successfully
In the action handler for user authenticated successfully you should check if there's a pending action
If there is a pending action or pending user intent perhaps is a better way to phrase that
Then return an effect from the user authenticated successfully action handler and the reducer
To satisfy the users intent
In this particular case we return an effect they would say continue booking process
There would be a action handled by your booking process feature where you would then set the state in a way that would trigger a screen where the user could confirm their booking

I'm out of time now I really hope that helps please let me know if you have any questions or want me to leave this thread alone
But I think it would be interesting to see more detailed domain modeling exercises for complex flows such as this one

A buddy just showed me the other day a project he's working on model the entity component system from reality kit and the compostable architecture and he said it's made his development flown incredibly streamlined but I know he struggled a lot to set it up because it was very complex of a domain

Let me know if any of that helped

I’m not sure we should think about the concrete Dependency we use as persistence engine with this much importance when modeling our domain.

Instead, for every feature reducer that requires information about the user, we should read that information from state (which as you said will be acquired by our authentication client)

Let me know what you think about that. Most likely, we will end up implementing a PersistenceClient whose main live implementation will be user defaults, but that may as well be firebase or SwiftData or sqlite. One of the main points of The Composable Architecture feom My perspective is it makes that choice somewhat irrelevant.

The AuthenticationFeature I've mentioned can potentially be, for instance, a LoginFeature domain to handle the state, actions, reducer, navigation flow and screens. And the PersistenceClient you've mentioned can be potentially modeled as a dependency.