Updating Environment Variables

Has anyone else come across a need to update environment variables? For example, I have an apiService: ServiceType that is responsible for making all of my network calls. Pre-TCA, I had a login function that would accept a sessionToken, which would then be sent along with each request.

Since the Environment is passed into each reducer as a constant, I don't think it's possible to keep doing that. I'm curious if this is a challenge that anyone else has run into or if I need to change my thinking with this architecture.

One option would be to keep the sessionToken in one of the reducer's states and then pass that along with each request, but that would be a bit tedious and it feels like there is a better way. I just haven't found it yet.

1 Like

I'm not sure modifying the environment is a good idea, but as far as I know, nothing stops you from declaring it a class instead of a struct, or declaring it a struct with a property that references a class.

In a recent project, I added a user defaults client to the Environment and use that to get access to the session token in the reducer.

While passing the sessionToken in to each service call is a little tedious it does mean that the compiler can check if you have one when you make the call. If you keep the state in the environment then you run the risk of creating code paths that make api calls when the sessionToken isn't available yet.

1 Like

A pattern that's worked well for us is for the "live" implementation of a dependency to manage some fileprivate, mutable state. If your app only has to worry about a single session token, this could be held onto in a single var:

// LiveClient.swift

// Token used to authenticate logged-in requests
private var token: String?

// Helper function to create effects for API requests
private func request<T>(
  for path: String
) -> Effect<T, ClientError> where T: Decodable {
  // use `token` to authenticate requests here
  ...
}

extension Client {
  // "Live" client, which provides APIs for setting this
  // token and uses the `request` helper above
  public static let live = Client(
    authenticate: { token = $0 },
    ...
  )
}
1 Like

Can you put this into a case study? I can't get my head around how exactly it should work.

Imagine I have a LiveClient which is previously intialise without the token and embedded in the Environment. How can I replace the client inside the Envirionment with the new live one?

3 Likes

I would also love to hear more about your approach @stephencelis...

How does the token get set? Do you pass it around as state?

2 Likes

I am not sure that anyone has solutions for the Token issue? What I did is use BaseState with the embedded token inside and each client has to have one more "Token" parameter. So annoying but at least it works for me

If the type handling networking and/or building requests defines an instance member for the access token, I believe the token should be set on the Environment member when initialized. Which, depending on the exact use case, is generally in the pullback. This is because the environment is passed in to the reducer to operate on, then it is deallocated.

The other option is to pass the token as a parameter for each function call to your type handling networking and/or building requests.

I think you can use the same technique that is used in this answer.