I am having a fixed AppBar()
and BottomNavigationBar()
and only changing the body for whole Application.
To achieve this I have used the builder:
property in the MaterialApp()
Widget.
Now when I tap on the TextField
it throws the hasSize
error. when I remove the FocusNode()
from the TextField
OR instead of storing the FocusNode()
in a variable directly assigning like focusNode: FocusNode()
removes the error. So, Why the focus node is throwing this error and what should I do to solve this.
CommonTextFormField:
class CommonTextFormField extends StatelessWidget {
const CommonTextFormField({
Key? key,
required this.controller,
this.hint,
required this.keyboardType,
this.obscureText,
required this.focusBorderColor,
this.onChanged,
this.validator,
this.onTap,
this.suffixIcon,
this.readOnly,
this.inputFormatters,
this.onFieldSubmitted,
this.focusNode,
this.borderColor,
this.borderWidth = 1,
this.errorMessage,
this.autoFocus,
this.maxLines = 1,
}) : super(key: key);
final String? hint;
final TextEditingController controller;
final FocusNode? focusNode;
final TextInputType keyboardType;
final bool? obscureText;
final Color? borderColor;
final Color focusBorderColor;
final double borderWidth;
final Widget? suffixIcon;
final ValueChanged<String>? onChanged;
final Function(String)? onFieldSubmitted;
final FormFieldValidator<String>? validator;
final GestureTapCallback? onTap;
final bool? readOnly;
final String? errorMessage;
final bool? autoFocus;
final List<TextInputFormatter>? inputFormatters;
final int? maxLines;
@override
Widget build(BuildContext context) {
return TextField(
key: key,
readOnly: readOnly ?? false,
controller: controller,
focusNode: focusNode,
autofocus: autoFocus ?? false,
cursorColor: kBlack45,
keyboardType: keyboardType,
obscureText: obscureText ?? false,
onTap: onTap ?? () {},
onChanged: onChanged,
onSubmitted: onFieldSubmitted ?? (_) {},
textAlignVertical: TextAlignVertical.center,
inputFormatters: inputFormatters ?? [],
maxLines: maxLines,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 10),
semanticCounterText: hint,
hintText: hint,
hintStyle: const TextStyle(color: kBlack45),
errorText: errorMessage,
errorMaxLines: errorMessage != null ? 1 : null,
errorStyle: const TextStyle(color: kOrange),
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
BorderSide(color: borderColor ?? kBlack45, width: borderWidth),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
BorderSide(color: borderColor ?? kBlack45, width: borderWidth),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: focusBorderColor, width: borderWidth),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
BorderSide(color: borderColor ?? kBlack45, width: borderWidth),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
BorderSide(color: borderColor ?? kBlack45, width: borderWidth),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: focusBorderColor, width: borderWidth),
),
),
);
}
}
Main Function:
void main() async {
navigationServiceController.registerLazySingleton(() => NavigationService());
WidgetsFlutterBinding.ensureInitialized();
prefs = await SharedPreferences.getInstance();
if (kIsWeb) {
setUrlStrategy(PathUrlStrategy());
}
await Firebase.initializeApp(
name: "T2G", options: DefaultFirebaseOptions.currentPlatform);
await getFlagList();
await gettingLanguage();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(const Inizilation());
}
class Inizilation extends StatelessWidget {
const Inizilation({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: initialPage(),
initialBinding: InitialBinding(),
title: "T2G",
debugShowCheckedModeBanner: false,
theme: ThemeData(
scrollbarTheme: const ScrollbarThemeData(
thickness: MaterialStatePropertyAll<double>(0),
trackVisibility: MaterialStatePropertyAll<bool>(false),
thumbVisibility: MaterialStatePropertyAll<bool>(false),
),
fontFamily: "AvenirLTDStd",
appBarTheme: const AppBarTheme(backgroundColor: kWhite),
primaryColor: kOrange,
primarySwatch: Colors.red,
applyElevationOverlayColor: true,
focusColor: Colors.transparent,
iconTheme: const IconThemeData(
color: kBlack,
),
),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale("de", "DE"),
Locale("en", "US"),
Locale("fr", "FR"),
Locale("nl", "NL"),
Locale("pt-br", "pt-BR"),
],
scrollBehavior: RemoveGlowEffect().copyWith(scrollbars: false),
enableLog: true,
defaultTransition: Transition.fade,
transitionDuration: const Duration(milliseconds: 500),
builder: (BuildContext context, Widget? child) =>
MainLayout(child: child),
key: navigatorKey,
onGenerateRoute:
navigationServiceController<NavigationService>().generateRoute,
);
}
}
Navigation Function:
class NavigationService {
Future<dynamic> navigateTo(String routeName) {
return Get.toNamed(routeName)!;
}
void navigateBack() {
return Get.back();
}
void splashScreenPage() {
navigateTo(splashScreen);
}
void emailPage() {
enableLanguage.value = true;
enableAppBar.value = true;
commonBottomBarViewModel.title.value = false;
navigateTo(emailCheckScreen);
}
void passwordPage() {
navigateTo(passwordScreen);
}
void registerPage() {
navigateTo(registerScreen);
}
void forgotEmailTriggerPage() {
navigateTo(forgotPasswordEmailScreen);
}
void forgotPasswordPage() {
navigateTo(forgotPasswordScreen);
}
void taskListPage() {
enableLanguage.value = false;
enableBottomBar.value = true;
commonBottomBarViewModel.title.value = true;
navigateTo(taskListScreen);
}
void taskDetailsPage() {
navigateTo(taskDetailsScreen);
}
void actualDurationPage() {
navigateTo(actualDurationScreen);
}
void extraAmountPage() {
navigateTo(extraAmountScreen);
}
void signaturePage() {
navigateTo(signatureScreen);
}
void taskComplitionPage() {
navigateTo(taskCompletionScreen);
}
void logoutPage() {
enableLanguage.value = true;
enableBottomBar.value = false;
navigateTo(logoutScreen);
}
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case splashScreen:
{
return GetPageRoute(
routeName: splashScreen,
page: () => const SplashScreen(),
binding: SplashScreenBinding(),
settings: settings,
);
}
case passwordScreen:
{
return GetPageRoute(
routeName: passwordScreen,
page: () => const PasswordPage(),
settings: settings,
);
}
case registerScreen:
{
return GetPageRoute(
routeName: registerScreen,
page: () => const AccountRegister(),
settings: settings,
);
}
case forgotPasswordEmailScreen:
{
return GetPageRoute(
routeName: forgotPasswordEmailScreen,
page: () => const ForgotEmailTrigger(),
settings: settings,
);
}
case forgotPasswordScreen:
{
return GetPageRoute(
routeName: forgotPasswordScreen,
page: () => const ForgotEmailTriggerConfirmation(),
settings: settings,
);
}
case taskListScreen:
{
return GetPageRoute(
routeName: taskListScreen,
page: () => const TaskListPage(),
binding: TaskListBinding(),
settings: settings,
);
}
case taskDetailsScreen:
{
return GetPageRoute(
routeName: taskDetailsScreen,
page: () => const TaskDetailsPage(),
binding: TaskDetailsBinding(),
settings: settings,
);
}
case actualDurationScreen:
{
return GetPageRoute(
routeName: actualDurationScreen,
page: () => const ActualDuration(),
binding: TaskDetailsBinding(),
settings: settings,
);
}
case extraAmountScreen:
{
return GetPageRoute(
routeName: extraAmountScreen,
page: () => const ExtraAmount(),
binding: ExtraAmountBinding(),
settings: settings,
);
}
case signatureScreen:
{
return GetPageRoute(
routeName: signatureScreen,
page: () => const Signature(),
binding: SignatureBinding(),
settings: settings,
);
}
case taskCompletionScreen:
{
return GetPageRoute(
routeName: taskCompletionScreen,
page: () => const TaskComplition(),
binding: TaskCompletionBinding(),
settings: settings,
);
}
case inAppNotificaitonScreen:
{
return GetPageRoute(
routeName: inAppNotificaitonScreen,
page: () => const InAppNotification(),
settings: settings,
);
}
case profileScreen:
{
return GetPageRoute(
routeName: profileScreen,
page: () => const ProfilePage(),
binding: ProfileBinding(),
settings: settings,
);
}
default:
{
return GetPageRoute(
routeName: emailCheckScreen,
page: () => const EmailCheck(),
binding: LoginBinding(),
settings: settings,
);
}
}
}
}
Main Layout:
class MainLayout extends StatelessWidget {
const MainLayout({super.key, required this.child});
final Widget? child;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
navigationServiceController<NavigationService>().navigateBack();
return true;
},
child: Scaffold(
appBar: const CommonAppBar(),
body: child,
bottomNavigationBar: const CommonBottomBar()),
);
}
}
Email Check Page:
class EmailCheck extends StatelessWidget {
const EmailCheck({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return GetBuilder<LoginViewModel>(
init: LoginViewModel(),
builder: (loginViewModel) {
return Center(
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Container(
width: width < 480 ? width : 400,
height: height,
padding: const EdgeInsets.all(10),
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox.fromSize(
size: Size.fromHeight(height * 0.1),
),
Image.asset(
kSearchFilterImage,
width: width * 0.4,
height: height * 0.2,
),
Padding(
padding: const EdgeInsets.only(bottom: 10, top: 10),
child: Obx(
() => CommonText(
text: kLanguage.value.loginTextInfo,
size: 16,
fontWeight: FontWeight.bold,
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Obx(
() => CommonText(
text: kLanguage.value.enterEmail,
textAlign: TextAlign.left,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Obx(
() => CommonTextFormField(
controller: loginViewModel.emailController,
focusNode: loginViewModel.emailFocusNode,
hint: kLanguage.value.knownemail,
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (email) =>
kIsWeb ? loginViewModel.validateEmail() : () {},
focusBorderColor: kBlack45,
),
),
),
Obx(
() => LoadingButton(
status: loginViewModel.buttonLoading,
onTap: () => loginViewModel.validateEmail(),
text: kLanguage.value.ok),
),
],
),
),
),
);
},
);
}
}
Error:
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for FocusNode:
RenderBox was not laid out: RenderEditable#d95c6 NEEDS-LAYOUT NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
box.dart:1
Failed assertion: line 2009 pos 12: 'hasSize'
2
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=2_bug.md
When the exception was thrown, this was the stack
#2 RenderBox.size
box.dart:2009
#3 EditableTextState._updateSizeAndTransform
editable_text.dart:3527
#4 EditableTextState._openInputConnection
editable_text.dart:2920
#5 EditableTextState._openOrCloseInputConnectionIfNeeded
editable_text.dart:2955
#6 EditableTextState._handleFocusChanged
editable_text.dart:3442
#7 ChangeNotifier.notifyListeners
change_notifier.dart:381
#8 FocusNode._notify
focus_manager.dart:1038
#9 FocusManager._applyFocusChange
focus_manager.dart:1803
(elided 4 frames from class _AssertionError and dart:async)
The FocusNode sending notification was: FocusNode#4f729([PRIMARY FOCUS])
context: Focus
PRIMARY FOCUS
════════════════════════════════════════════════════════════════════════════════
In your case, it seems that storing the FocusNode in a variable is causing the issue. This is because the FocusNode is being created before the TextField is added to the widget tree, and hence it is not properly laid out.
To resolve this issue, you can try defining the FocusNode inside the build method of the widget that contains the TextField. This way, the FocusNode will be created after the widget is added to the widget tree and will have the correct layout constraints.
Here is an example:
Alternatively, you can try wrapping the TextField with a LayoutBuilder widget and pass the constraints to the TextField's container. This way, the TextField will have the correct layout constraints and the error should be resolved.