GraphQL and Bloc in Flutter

GraphQL and Bloc in Flutter


0

So im trying to implement graphql in flutter, using bloc too. I think i already did all the necessary part, but when i try to hit the graphql, it return response like this

HttpLinkParserException (ResponseFormatException(originalException: FormatException: Unexpected character (at character 1)

here is my graphql function –

void _onLoginButtonPressed(
      LoginButtonPressed event, Emitter<LoginState> emit) async {
    emit(LoginLoading());
    try {
      // Define your login mutation
      final String loginMutation = '''
        mutation Login($uname: String!, $pass: String!, $idNum: String!, $deviceId: String!) {
          login(uname: $uname, pass: $pass, idNum: $idNum, deviceId: $deviceId) {
            accessToken
            refreshToken
          }
        }
      ''';

      // Define variables for your mutation
      final Map<String, dynamic> variables = {
        'uname': event.username,
        'pass': event.password,
        'idNum': event.idNum,
        'deviceId': event.deviceId,
      };

      // Execute the login mutation and get the response
      final QueryResult result = await graphQLClient.mutate(
        MutationOptions(
          document: gql(loginMutation),
          variables: variables,
        ),
      );

      if (result.hasException) {
        print(result.exception!);
      } else {
        // Extract access token and refresh token from the response
        final accessToken = result.data?['login']['accessToken'];
        final refreshToken = result.data?['login']['refreshToken'];
        emit(
            LoginSuccess(accessToken: accessToken, refreshToken: refreshToken));
      }
    } catch (e) {
      emit(LoginFailure(error: e.toString()));
    }
  }

this is the response when I try using postman

{
"data": {
"login": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVbmFtZSI6ImRlbyIsIlJlZ2lzdGVyZWRDbGFpbXMiOnsiaXNzIjoiYnJtb2JpbGUiLCJleHAiOjE2OTY5MDkwNTZ9fQ.XT57BIVfR6Ct8IIf5zHicqhwIqF_J9Ckj9FWflzQdwc",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVbmFtZSI6ImRlbyIsIlJlZ2lzdGVyZWRDbGFpbXMiOnsiaXNzIjoiYnJtb2JpbGUiLCJleHAiOjE2OTY5MjM0NTZ9fQ.MTBtgeKe9cgrPE4CnCPG7Z4vagi9_7qm4PDT4aJ6Nbo"
}
}
}

this is my graphql setup, on the main file:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initializes Hive with a valid directory in your app files
  await Hive.initFlutter();
  await Hive.openBox("userBox");
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GraphQLClient client = GraphQLClient(
    link: HttpLink('https://127.0.0.1:7130/graphql/'),
    cache: GraphQLCache(),
  );

  final AuthLink authLink = AuthLink(
    getToken: () async {
      final box = Hive.box('userBox');
      final token = box.get('accessToken');
      return 'Bearer $token';
    },
  );

  late final ValueNotifier<GraphQLClient> clientNotifier =
      ValueNotifier<GraphQLClient>(client);

  @override
  Widget build(BuildContext context) {
    return GraphQLProvider(
      client: clientNotifier,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routes: {
          '/home': (context) => const Scaffold(
                body: Center(
                  child: Text('Home'),
                ),
              ),
        },
        home: BlocProvider(
          create: (context) => LoginBloc(graphQLClient: client),
          child: LoginScreen(),
        ),
      ),
    );
  }
}

This is my widget where I use the bloc

class _LoginScreenState extends State<LoginScreen> {
  TextEditingController usernameController = TextEditingController();
  TextEditingController passwordController = TextEditingController();
  TextEditingController idNumController = TextEditingController();
  TextEditingController deviceIDController = TextEditingController();
  @override
  Widget build(BuildContext context) {
    final loginBloc = BlocProvider.of<LoginBloc>(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Login'),
      ),
      body: Center(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: BlocBuilder<LoginBloc, LoginState>(
                builder: (context, state) {
                  if (state is LoginLoading) {
                    return const CircularProgressIndicator();
                  } else if (state is LoginSuccess) {
                    print('object');
                    Navigator.of(context).pushReplacement(
                      MaterialPageRoute(
                          builder: (context) =>
                              HomeScreen()), // Replace with your desired screen
                    );
                  } else if (state is LoginFailure) {
                    return Text('Login Failed: ${state.error}');
                  }
                  return Container(
                    child: Column(
                      children: [
                        TextField(
                          controller: usernameController,
                          decoration: const InputDecoration(
                            labelText: 'Username',
                          ),
                        ),
                        TextField(
                          controller: passwordController,
                          decoration: const InputDecoration(
                            labelText: 'Password',
                          ),
                        ),
                        TextField(
                          controller: idNumController,
                          decoration: const InputDecoration(
                            labelText: 'IdNum',
                          ),
                        ),
                        TextField(
                          controller: deviceIDController,
                          decoration: const InputDecoration(
                            labelText: 'DeviceId',
                          ),
                        ),
                        ElevatedButton(
                          onPressed: () {
                            final username = usernameController.text;
                            final password = passwordController.text;
                            final deviceId = deviceIDController.text;
                            final idNum = idNumController.text;

                            loginBloc.add(LoginButtonPressed(
                              username: username,
                              password: password,
                              idNum: idNum,
                              deviceId: deviceId,
                            ));
                            Navigator.pop(context);
                          },
                          child: const Text('Login'),
                        ),
                      ],
                    ),
                  );
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

9

  • what does it print to the console when you do print(result.data) Since i know graph likes to nest it results pretty weirdly

    – CStark

    yesterday


  • it throws error after the result, the code stops there

    – foreverLearnerDevs

    yesterday

  • ah ok, so your graphqlclient setup could be the issue. Can you include how you setup the graphqlclient, you can remove any sensitive urls. Also are you using graphql or graphql-flutter package?

    – CStark

    yesterday


  • Ok I will edit and Include it, I think so too. I tested it on the local server tho, so it's fine. Im using GraphQL-Flutter, is there any difference between the two of it?

    – foreverLearnerDevs

    yesterday

  • I edited the question @CStark

    – foreverLearnerDevs

    yesterday


1 Answer
1


0

Below is a solution (you might need to fix some imports) where the build is being forced to wait for the graphclient to be created.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initializes Hive with a valid directory in your app files
  await Hive.initFlutter();
  await Hive.openBox("userBox");
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

    Future<ValueNotifier<GraphQLClient>> buildGraphClient() async {
        await initHiveForFlutter();

        final HttpLink httpLink = HttpLink('https://127.0.0.1:7130/graphql/');

        final AuthLink authLink = AuthLink(
            getToken: () async {
            final box = Hive.box('userBox');
            final token = box.get('accessToken');
            return 'Bearer $token';
            },
        );

        final Link link = authLink.concat(httpLink);

        return ValueNotifier(
            GraphQLClient(
                cache: GraphQLCache(store: HiveStore()),
                link: link,
            ),
        );
    }



  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: buildGraphClient(),
        builder: (context, snapshot) {

            if (snapshot.hasData) {
                return GraphQLProvider(
                    client: snapshot.data,
                    child: MaterialApp(
                        title: 'Flutter Demo',
                        theme: ThemeData(
                        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
                        useMaterial3: true,
                        ),
                        routes: {
                        '/home': (context) => const Scaffold(
                                body: Center(
                                child: Text('Home'),
                                ),
                            ),
                        },
                        home: BlocProvider(
                        create: (context) => LoginBloc(graphQLClient: client),
                        child: LoginScreen(),
                        ),
                    ),
                    );
                }
            }

            return Center(
                child: CircularProgressSpinner(),
            );
        }
    )

    
}

5

  • on the blocProvider, what should i pass inside loginbloc parameter?

    – foreverLearnerDevs

    yesterday

  • oh whoops forgot to update that, should be the same snapshot.data i think. But depending on what type the input is you could also do GraphQLProvider.of(context).value if you get a type error.

    – CStark

    yesterday


  • sir, now i got this error Tried to listen to an InheritedWidget in a life-cycle that will never be called again.

    – foreverLearnerDevs

    yesterday

  • from what i seeing looks it good be an issue somewhere in the blockProvider

    – CStark

    13 hours ago

  • I think i put the blocProvider at the top widget (in main file) why its still error

    – foreverLearnerDevs

    3 hours ago



Leave a Reply

Your email address will not be published. Required fields are marked *