Shourya Shikhar
Shourya Shikhar

Reputation: 1442

Keyboard opening and closing immediately due to multiple rebuilds when tapping on TextFormField

For debugging purposes, I had put a print statement inside the build method and I noticed that the whole widget was rebuilding multiple times when the TextFormField was tapped.

enter image description here

The keyboard opens up, and then immediately closes.

This is my code:

class LoginScreen extends StatelessWidget {
  LoginScreen({super.key});

  static Route<void> route() {
    return MaterialPageRoute<void>(builder: (_) => LoginScreen());
  }

  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _selectUrl = TextEditingController();

  @override
  Widget build(BuildContext context) {
    if (!kReleaseMode) {
      _emailController.text = '';
      _passwordController.text = '';
      _selectUrl.text = baseUrl;
    }

    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Align(
                  alignment: Alignment.center,
                  child: Padding(
                    padding: const EdgeInsets.fromLTRB(
                      20.0,
                    ),
                    child: Image.asset(
                      'assets/images/logo.png',
                      color: isDarkMode(context)
                          ? Theme.of(context).colorScheme.primary
                          : null,
                    ),
                  ),
                ),
                TextFormField(
                  controller: _emailController,
                  decoration: const InputDecoration(
                    hintText: 'Enter your email',
                  ),
                ),
                TextFormField(
                  controller: _passwordController,
                  decoration: const InputDecoration(
                    hintText: 'Enter your password',
                  ),
                  obscureText: true,
                ),
                BlocConsumer<AuthenticationBloc, AuthenticationState>(
                  listener: (context, state) {
                    if (state is AuthenticationFailureState) {
                      _emailController.text = state.fields['email'] ?? '';
                      _passwordController.text = state.fields['password'] ?? '';
                      showDialog(
                          context: context,
                          builder: (context) {
                            return AlertDialog(
                                title: const Text('Login failed'),
                                content: Text(
                                  state.errorMessage,
                                  textAlign: TextAlign.center,
                                ),
                                icon: const Icon(
                                  Icons.error,
                                  color: Colors.red,
                                ));
                          });
                    } else if (state is AuthenticationSuccessState) {
                      context
                          .read<CalendarDataBloc>()
                          .add(FetchCalendarEvents());

                      context.read<HomeBloc>().add(FetchCheckedInEvents());

                      Navigator.pushReplacementNamed(context, '/home');
                    }
                  },
                  builder: (context, state) {
                    return state is AuthenticationLoadingState &&
                            (state).isLoading
                        ? const Center(
                            child: CircularProgressIndicator(),
                          )
                        : SizedBox(
                            child: FilledButton(
                              onPressed: () {
                                BlocProvider.of<AuthenticationBloc>(context)
                                    .add(
                                  LoginUser(
                                    _emailController.text.trim(),
                                    _passwordController.text.trim(),
                                  ),
                                );
                              },
                              child: const Text(
                                'Login',
                                style: TextStyle(
                                  fontSize: 20,
                                ),
                              ),
                            ),
                          );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

This is my main.dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  SecureStorageService.instance;
  await HiveStorageService.init();
  final ThemeMode initialThemeMode = HiveStorageService().getThemeMode();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  if (Platform.isAndroid) {
    await FirebaseNotifications().initNotifications();
  }

  if (!kDebugMode) {
    FlutterError.onError = (errorDetails) {
      FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
    };

    PlatformDispatcher.instance.onError = (error, stack) {
      FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
      return true;
    };

    await SentryFlutter.init(
      (options) {
        options.dsn =
            '';
        options.tracesSampleRate = 1.0;
        options.profilesSampleRate = 1.0;
        options.environment = kReleaseMode ? 'production' : 'profile';
      },
      appRunner: () => runApp(MultiBlocProvider(
        providers: [
          BlocProvider(
            create: (context) => AuthenticationBloc()..add(const AppStarted()),
          ),
          BlocProvider<HomeBloc>(create: (context) => HomeBloc()),
          BlocProvider<CalendarBloc>(create: (context) => CalendarBloc()),
          BlocProvider<CalendarDataBloc>(
              create: (context) => CalendarDataBloc()),
          BlocProvider(
              create: (_) => ThemeCubit()..setThemeMode(initialThemeMode)),
        ],
        child: const MyApp(),
      )),
    );
  } else {
    runApp(MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => AuthenticationBloc()..add(const AppStarted()),
        ),
        BlocProvider<HomeBloc>(create: (context) => HomeBloc()),
        BlocProvider<CalendarBloc>(create: (context) => CalendarBloc()),
        BlocProvider<CalendarDataBloc>(create: (context) => CalendarDataBloc()),
        BlocProvider(
            create: (_) => ThemeCubit()..setThemeMode(initialThemeMode)),
      ],
      child: const MyApp(),
    ));
  }
}

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

  static const platform = MethodChannel('in.co.ezerx.sales_iq/check');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: getTheme(context, currentTheme: context.watch<ThemeCubit>().state),
      home: Platform.isAndroid
          ? FutureBuilder<bool>(
              future: _isDeveloperOptionsEnabled(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Scaffold(
                    body: Center(
                      child: CircularProgressIndicator(),
                    ),
                  );
                } else if (snapshot.hasError) {
                  return const Scaffold(
                    body: Center(
                      child: Text('Error checking developer options'),
                    ),
                  );
                } else if (snapshot.data == true && kReleaseMode) {
                  return const Scaffold(
                    body: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text('Developer options are enabled'),
                          Text('Please disable developer options to continue'),
                        ],
                      ),
                    ),
                  );
                } else {
                  return BlocBuilder<AuthenticationBloc, AuthenticationState>(
                      builder: (context, state) {
                    if (state is AuthenticationSuccessState) {
                      return const HomeScreen();
                    } else {
                      return LoginScreen();
                    }
                  });
                }
              })
          : BlocBuilder<AuthenticationBloc, AuthenticationState>(
              builder: (context, state) {
              if (state is AuthenticationSuccessState) {
                return const HomeScreen();
              } else {
                return LoginScreen();
              }
            }),
      onGenerateRoute: (setting) => _generateRoute(setting),
    );
  }

  Future<bool> _isDeveloperOptionsEnabled() async {
    try {
      final bool result = await platform.invokeMethod('checkDevOptions');
      return result;
    } on PlatformException catch (e) {
      errorPrint(e.message);
      return false;
    }
  }

  Route _generateRoute(RouteSettings settings) {
    Widget screen;

    switch (settings.name) {
      case '/':
        screen = LoginScreen();
        break;
      case '/home':
        screen = const HomeScreen();
        break;
      case '/settings':
        screen = const SettingsScreen();
        break;
      case '/checkin':
        screen = const Checkin();
        break;
      case '/checkout':
        screen = Checkout();
        break;
      case '/prospect':
        screen = Prospect();
        break;
      case '/create_event':
        screen = CreateEvent();
        break;
      case '/reschedule':
        screen = Reschedule();
        break;
      case '/prospect_list':
        screen = ProspectList();
        break;
      case '/profile':
        screen = const Profile();
        break;
      case '/dashboard':
        screen = const Dashboard();
        break;
      case '/cumulative_dashboard':
        screen = const CumulativeDashboard();
        break;
      default:
        screen = LoginScreen();
    }

    return PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => screen,
      settings: settings,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        const begin = Offset(1.0, 0.0);
        const end = Offset.zero;
        const curve = Curves.ease;

        var tween =
            Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

        return SlideTransition(
          position: animation.drive(tween),
          child: child,
        );
      },
    );
  }
}

Upvotes: -1

Views: 78

Answers (2)

Dipak Prajapati
Dipak Prajapati

Reputation: 154

Convert your stateless widgets into State full widgets override init method. Remove your build method code and covert to like this.

 @override
  initState() {
       if (!kReleaseMode) {
      _emailController.text = '';
      _passwordController.text = '';
      _selectUrl.text = baseUrl;
},}

Upvotes: 0

WaLeed Ahmad
WaLeed Ahmad

Reputation: 177

Check Bloc State Changes If your AuthenticationBloc is emitting new states frequently (such as AuthenticationLoadingState), it could be triggering rebuilds too often.

Upvotes: 0

Related Questions