1

I am trying to navigate after login inside futurebuilder. api request done successfully but navigation getting error while testing. Help me How to use futurebuilder properly.

child: ElevatedButton(
                onPressed: () {
                  // Validate returns true if the form is valid, or false otherwise.
                  if (_mobileKey.currentState!.validate()) {
                    FutureBuilder<Loginuser>(
                      future: loginuser(mobileController.text.toString(),
                          passwordController.text.toString()),
                      builder: (context, snapshot) {
                        if (snapshot.hasData) {
                          context.go('/Home');
                          return Text(snapshot.data!.message);
                        } else if (snapshot.hasError) {
                          return Text('${snapshot.error}');
                        }

                        // By default, show a loading spinner.
                        return const CircularProgressIndicator();
                      },
                    );
                  }

                  context.go('/Home');
                },
                child: const Text('Submit')),

I tried this its not working. I am using " go_router: ^5.2.4 " for navigation

krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
Hari .S
  • 59
  • 6
  • 1
    do not use builder inside function. if you want to validate login, you can use `Future` funtion. and for widget loading, just call `setState` to update condition – pmatatias Dec 29 '22 at 01:49
  • 1
    `FutureBuilder` has return type of `Widget`, you have to omit doing this way, use instead simple `Future`, for your case you can go with `Future` – Azhar Husain Raeisi Dec 29 '22 at 04:47
  • i cant understand short answer. i am not a programmer. give complete solution with example code.@pmatatias @Azhar Husain Raeisi – Hari .S Jan 02 '23 at 00:31

3 Answers3

4

TLDR: Add:

 WidgetsBinding.instance.addPostFrameCallback((_) =>
                context.go('/Home'));

to the FutureBuilder


I managed to reproduce the problem with this example. Let's take a look.

If you run this code:

import 'package:flutter/material.dart';

void main() => runApp(MainApp());

class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(body: Test()),
    );
  }
}

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: printHello(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            Navigator.push(
                context, MaterialPageRoute(builder: (context) => HomePage()));

            return Text(snapshot.data.toString());
          } else if (snapshot.hasError) {
            return Text("error");
          } else {
            return Center(child: CircularProgressIndicator());
          }
        });
  }
}

Future<String> printHello() async {
  return Future.value("Hello");
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: const Text("home page"));
  }
}

You'll see an error:

setState() or markNeedsBuild() called during build.

So, to fix the problem, you need to use WidgetsBinding.instance.addPostFrameCallback:

WidgetsBinding.instance.addPostFrameCallback((_) =>
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return HomePage();
                })));

but for your example for go_router, add this line:

 WidgetsBinding.instance.addPostFrameCallback((_) =>
                context.go('/Home'));

Complete working example:

import 'package:flutter/material.dart';

void main() => runApp(MainApp());

class MainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(body: Test()),
    );
  }
}

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: printHello(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            WidgetsBinding.instance.addPostFrameCallback((_) =>
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return HomePage();
                })));

            return Text(snapshot.data.toString());
          } else if (snapshot.hasError) {
            return Text("error");
          } else {
            return Center(child: CircularProgressIndicator());
          }
        });
  }
}

Future<String> printHello() async {
  return Future.value("Hello");
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: const Text("home page"));
  }
}

See also

setState() or markNeedsBuild called during build

MendelG
  • 14,885
  • 4
  • 25
  • 52
1

Try removing the FutureBuilder from inside the ElevatedButton instead use Promises i.e then/catch to navigate to new screen

Updated code:

child ElevatedButton(
        child: Container(),
        onPressed: () async {
           // Remove the future builder from here
          await loginuser(mobileController.text.toString(),
                  passwordController.text.toString())
              .then((result) {       
                 context.go('/Home');             //  add your navigatin inside then block 
              }).onError((error, stackTrace) {
            print(error);
          });
krishnaacharyaa
  • 14,953
  • 4
  • 49
  • 88
0

This method help you to navigate the route without FutureBuilder. see the code

onPressed: () async {
          // then await the future You want to complete and then use `.then()` 
          //method to implement the code that you want to implement when the future is completed
          await //call your future widget //
              .then((result) {
            print('future completed');
          // Navigate here 
            // For errors use onError to show or check the errors.
          }).onError((error, stackTrace) {
            print(error);
          });
        }
Hari .S
  • 59
  • 6
  • 1
    Hello @hari, i have added almost the same code as yours with proper explanation 1 day ago, hope you consider it and upvote and accept it if it worked – krishnaacharyaa Jan 06 '23 at 00:45