Is your feature request related to a problem? Please describe.
I am trying to build a multi tenant setup using this awesome library. When the user logs in this tenant key will be stored in shared prefs. This key will then be part of my uri which is like 'https://$tenantKey.server.com'. I want this uri to be set on an httpclient which wrapping my whole application and has the MaterialApp as a child and I am not sure how to do this.
Describe the solution you'd like
Be able to set the uri of the client dynamically or have an obvious way to change the uri of an active client.
Describe alternatives you've considered
Have you tried separating the authentication and authenticated routes? This way, you redirect user to the route section that is not wrapped by the GraphQLProvider Widget when they are not logged in. If they are logged in (i.e. the tenantKey exists) then construct you URL and create a HTTPLink, and allow access to authenticated section, and once a user logouts, clear shared prefs and redirect to logged out section.
Another choice you have is to use this package client, without a widget which will give you even more versatility.
Sounds like a good idea, thanks I am going to see if I can make that work!
Yeah, for this particular use case, authenticated routes is probably best.
This kind of HttpLink customization is also a good example of why it needs to be more extensible (#267)
Sorry for not replying any more! I tried to make this work but got kinda stuck at understanding how to implement this. From my understanding you are saying that if i have routes A, B and C where B and C need to have the GraphQLProvider that uses the tenant Key. How would I wrap them in a widget thats having the new provider? Right now the only graphqlProvider is wrapped around my MaterialApp which makes it accessible in every widget under it. How can I achieve the same thing here but just for a subset of routes? Since I am kinda new to flutter, your help would be appreciated! A code sample, links or an explanation would be awesome! Thanks in advance
You have to remove the widget at the very top of the widget tree, then place it somewhere in the widget tree that it will cover the routes you want to use graphql when you have the tenant key. The provider is just an inherited widget, for some cases, it makes sense to put it at the very top of the widget tree so all access to the client, but for others you can place somewhere else where it makes sense.
in my app, I have a simple AuthenticationProvider right below my MaterialApp, which is still below the ClientProvider:
ClientProvider(
uri: this.env.baseUrl + '/graphql',
child: MaterialApp(
title: 'savvy',
home: AuthenticationProvider(child: Home()),
),
)
Hypothetically, this would allow me to have non-authenticated routes where Bearer isn't checked (I don't have any yet though).
AuthProvider:import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:graphql_flutter/graphql_flutter.dart' show AuthLink;
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: <String>['email', 'profile'],
);
GoogleSignInAccount _currentUser;
final _onUserChange = _googleSignIn.onCurrentUserChanged.listen;
bool get isAuthenticated => _currentUser != null;
Future<String> get _token async {
final auth = await _googleSignIn.currentUser?.authentication;
return auth?.idToken;
}
final googleSignInLink = AuthLink(getToken: () async {
final token = await _token;
return 'Bearer $token';
});
class AuthenticationProvider extends StatefulWidget {
AuthenticationProvider({@required this.child});
final Widget child;
_AuthenticationProviderState createState() => _AuthenticationProviderState();
}
class _AuthenticationProviderState extends State<AuthenticationProvider> {
@override
void initState() {
super.initState();
_onUserChange((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
});
});
_googleSignIn.signInSilently(suppressErrors: true);
}
void _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
Widget get signInPage => Scaffold(
appBar: AppBar(
title: const Text('Google Sign In'),
),
body: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text("You are not currently signed in."),
RaisedButton(
child: const Text('SIGN IN'),
onPressed: _handleSignIn,
),
],
),
));
@override
Widget build(BuildContext context) {
return isAuthenticated ? widget.child : signInPage;
}
}
class SignOut extends StatelessWidget {
SignOut();
void _handleSignOut() async {
try {
await _googleSignIn.signOut();
} catch (error) {
print(error);
}
}
@override
Widget build(BuildContext context) {
return RaisedButton(
child: const Text('SIGN OUT'),
onPressed: _handleSignOut,
);
}
}
ClientProvider:import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:flutter/material.dart';
import './_cache.dart' show cache;
import './user_auth.dart' show googleSignInLink;
ValueNotifier<GraphQLClient> Client({@required String uri}) {
return ValueNotifier(
GraphQLClient(
cache: cache,
link: googleSignInLink.concat(
HttpLink(uri: uri, headers: {"Accept": "application/json"}) as Link),
),
);
}
/// Wraps the root application with the `graphql_flutter` client.
/// We use the cache for all state management.
class ClientProvider extends StatelessWidget {
final Widget child;
final ValueNotifier<GraphQLClient> client;
ClientProvider({
@required this.child,
@required String uri,
}) : client = Client(uri: uri);
@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: client,
child: child,
);
}
}
Thanks for the responses. There is one thing I still can not wrap my head around.
You have to remove the widget at the very top of the widget tree, then place it somewhere in the widget tree that it will cover the routes you want to use graphql when you have the tenant key.
But which Widget would be over the set of authenticated routes. The only widget that is above all routes is MaterialApp. I am not sure how one would place a set of widgets under a graphql widget without importing that graphql widget in all of the widgets.
Seems like I am missing something fundamental about Flutter here, so help would be awesome!
@lassesteffen I'd recommend my approach - there are other approaches to navigation that moving the client works for. You could use something like onGenerateRoute, to inject a component between a MaterialApp and its routes, but just handling the token outside the tree seems to work just fine for me.
@micimize i am wondering how you managed to use the cache provider in the previous example of yours !
@bicho19 I'm not using CacheProvider anywhere, but I think it should behave normally