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.
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,
);
},
);
}
}
2
Answers
Check Bloc State Changes If your AuthenticationBloc is emitting new states frequently (such as AuthenticationLoadingState), it could be triggering rebuilds too often.
Convert your stateless widgets into State full widgets override init method.
Remove your build method code and covert to like this.