I have an issue with my FLutter code that I am not able to solve neither to understand why this is happening.
First the code in questoin:
import 'dart:io';
import 'dart:ui';
import 'package:app/controller/api/api.dart';
import 'package:app/data/holder/sidebar_entries_holder.dart';
import 'package:app/data/holder/date_holder.dart';
import 'package:app/data/holder/user_holder.dart';
import 'package:app/data/models/user.dart';
import 'package:app/exceptions/login/not_logged_in_exception.dart';
import 'package:app/ui/views/login/login_view.dart';
import 'package:app/ui/views/responsive/home/mobile/home_mobile_view.dart';
import 'package:app/ui/views/responsive/responsive_view.dart';
import 'package:app/utils/common/common_functions.dart';
import 'package:app/utils/log/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:responsive_builder/responsive_builder.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Log.initialize();
HttpOverrides.global = MyHttpOverrides();
final userHolder = UserHolder();
final dateHOlder = DateHolder();
final sideBarEntriesHolder = SideBarEntriesHolder();
final selectedSideBarEntryHolder = SelectedSideBarEntryHolder();
final window = PlatformDispatcher.instance.views.first;
final screenWidth = window.physicalSize.width / window.devicePixelRatio;
final screenHeight = window.physicalSize.height / window.devicePixelRatio;
final isHandy = screenWidth < 600;
if (isHandy) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
} else {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
try {
final User user = await Api.getUserFromLocalDatabase();
userHolder.setUser(user);
Api.setUserHolder(userHolder);
} catch (e) {
if (e is NotLoggedInException) {
Log.warning(
"No user currently logged in. Will be forwarded to the login view");
} else {
Log.severe("Error $e will be forwarded to the login view");
}
}
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: userHolder),
ChangeNotifierProvider.value(value: dateHolder),
ChangeNotifierProvider.value(value: sideBarEntriesHolder),
ChangeNotifierProvider.value(value: selectedSideBarEntryHolder),
],
child: const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return const BaseLayout();
}
}
class BaseLayout extends StatefulWidget {
const BaseLayout({Key? key}) : super(key: key);
@override
_BaseLayoutState createState() => _BaseLayoutState();
}
class _BaseLayoutState extends State<BaseLayout> {
late SelectedSideBarEntryHolder selectedSideBarEntryHolder;
late UserHolder userHolder;
@override
void initState() {
super.initState();
selectedSideBarEntryHolder = context.read<SelectedSideBarEntryHolder>();
userHolder = context.read<UserHolder>();
}
@override
Widget build(BuildContext context) {
print("ME TOO");
final user = userHolder.user;
if (user != null && user.isAccessTokenValidForRestOfTheDay()) {
return Scaffold(
body: ResponsiveBuilder(builder: (context, sizingInformation) {
if (sizingInformation.isMobile) {
return const HomeMobileView();
} else if (sizingInformation.isTablet) {
return const ResponsiveView();
} else {
return Container();
}
}),
floatingActionButton: Consumer<SelectedSideBarEntryHolder>(
builder: (context, selectedSideBarEntryHolder, child) {
return Visibility(
visible: selectedSideBarEntryHolder.isNotSelected(),
child: FloatingActionButton(
onPressed: () {
CommonFunctions.showNewDialog(context);
},
child: const Icon(Icons.add),
),
);
},
),
);
} else {
return const LoginView();
}
}
}
import 'package:app/data/holder/sidebar_entries_holder.dart';
import 'package:app/data/holder/selected_side_bar_entry_holder.dart';
import 'package:app/data/holder/user_holder.dart';
import 'package:app/ui/components/appBar/app_bar.dart';
import 'package:app/ui/components/dateSelector/date_selector.dart';
import 'package:app/ui/components/sidebar/sidebar.dart';
import 'package:app/ui/views/responsive/mobile/edit_view.dart';
import 'package:app/ui/views/responsive/mobile/info_view.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:responsive_builder/responsive_builder.dart';
class HomeMobileView extends StatefulWidget {
const HomeMobileView({super.key});
@override
_HomeMobileView createState() => _HomeMobileView();
}
class _HomeMobileView extends State<HomeMobileView> {
late UserHolder userHolder;
late SideBarEntriesHolder sideBarEntriesHolder;
@override
void initState() {
super.initState();
userHolder = context.read<UserHolder>();
sideBarEntriesHolder = context.read<SideBarEntriesHolder>();
}
double _calculateSidebarWidth(
BuildContext context, DeviceScreenType deviceScreenType) {
var screenWidth = MediaQuery.of(context).size.width;
if (deviceScreenType == DeviceScreenType.mobile) {
return screenWidth;
} else if (deviceScreenType == DeviceScreenType.tablet) {
return screenWidth * 0.3;
} else {
return 300;
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) {
return Consumer<SelectedSideBarEntryHolder>(
builder: (context, selectedSideBarEntryHolder, child) {
if (selectedSideBarEntryHolder.isNotSelected()) {
return Scaffold(
appBar: const AppBar(),
body: Column(
children: [
const DateSelector(),
Expanded(
child: Sidebar(
sidebarWidth: _calculateSidebarWidth(
context, sizingInformation.deviceScreenType),
),
),
],
),
);
} else {
if (selectedSideBarEntryHolder.isInEditMode()) {
return const EditView();
} else {
return const Scaffold(
body: InfoView(),
);
}
}
},
);
},
);
}
}
import 'dart:async';
import 'package:app/data/holder/date_holder.dart';
import 'package:app/data/holder/selected_side_bar_holder.dart';
import 'package:app/ui/modals/error_dialog.dart';
import 'package:app/ui/modals/warning_dialog.dart';
import 'package:app/utils/log/log.dart';
import 'package:app/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:app/controller/api/api.dart';
import 'package:app/data/holder/sidebar_entries_holder.dart';
import 'package:app/data/holder/user_holder.dart';
import 'package:app/data/models/Entry.dart';
import 'package:app/ui/components/sidebar/sidebar_entry.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class Sidebar extends StatefulWidget {
final double sidebarWidth;
const Sidebar({
Key? key,
required this.sidebarWidth,
}) : super(key: key);
@override
_SidebarState createState() => _SidebarState();
}
class _SidebarState extends State<Sidebar> {
late UserHolder userHolder;
late DateHolder dateHolder;
late SideBarEntriesHolder sideBarEntriesHolder;
late SelectedSideBarEntryHolder selectedSideBarEntryHolder;
DateTime? lastUpdate;
@override
void initState() {
super.initState();
userHolder = context.read<UserHolder>();
dateHolder = context.read<DateHolder>();
sideBarEntriesHolder = context.read<SideBarEntriesHolder>();
selectedSideBarEntryHolder = context.read<SelectedSideBarEntryHolder>();
_reloadEntries();
}
Future<List<Entry>> _fetchEntries() async {
DateHolder dateHolder = Provider.of<DateHolder>(context, listen: false);
String formattedDate =
DateFormat('yyyy-MM-dd').format(dateHolder.currentDate);
List<Entry> entries = [];
try {
entries = await Api.getEntriesFromRestEndpoint(
formattedDate,
formattedDate,
);
final String formattedCurrentDate =
Utils.formatDate(dateHolder.currentDate);
sideBarEntriesHolder.setEntries(
formattedCurrentDate, entries);
return sideBarEntriesHolder.getEntries(formattedCurrentDate);
} catch (e) {
Log.severe('Error while fetching entries: $e');
return Future.error(e);
}
}
void _reloadEntries() {
setState(() {
lastUpdate = DateTime.now();
});
}
@override
Widget build(BuildContext context) {
print("I AM CALLED");
return Consumer<DateHolder>(
builder: (context, dateHolder, child) {
var futureEntries = _fetchEntries()();
return RefreshIndicator(
onRefresh: () async {
_reloadEntries();
},
child: FutureBuilder<List<Entry>>(
future: futureEntries,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return _buildListOnError(snapshot);
} else if (snapshot.hasData) {
return _buildList(snapshot.data!);
} else {
return _buildEmptyList();
}
},
),
);
},
);
}
Widget _buildListOnError(
final AsyncSnapshot<List<Entry>> snapshot) {
String currentDate = Utils.formatDate(dateHolder.currentDate);
bool listNotEmpty =
sideBarEntriesHolder.getEntries(currentDate).isNotEmpty;
if (snapshot.error is Exception && listNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
WarningDialog.show(
context,
"Fehler beim Laden der Daten",
"Wollen Sie es erneut versuchen ?",
"Ja, nochmal versuchen",
"Nein, mit den vorhandenen Daten weiterarbeiten",
onYesPressed: _reloadEntries);
});
return _buildList(
sideBarEntriesHolder.get(currentDate));
} else {
if (sideBarEntriesHolder.cached.containsKey(currentDate)) {
return _buildList(
sideBarEntriesHolder.cached[currentDate]!);
} else {
return _buildEmptyList();
}
}
}
Widget _buildList(List<Entry> entries) {
return entries.isEmpty
? _buildEmptyList()
: ListView.builder(
itemCount: entries.length,
itemBuilder: (context, index) {
final entry = entries[index];
return SidebarEntry(
entry: entry,
isSelected: false,
onTap: () async {
...
},
onLongPress: () {
showDeleteDialog(entry);
},
);
},
);
}
void showDeleteDialog(final Entry entry) async {
...
}
void showDeleteInformationDialog() {
...
}
Future<bool> deleteSideBarEntry(final Entry entry) async {
...
}
Widget _buildEmptyList() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Keine Einträge',
style: TextStyle(fontSize: 16),
),
ElevatedButton(
onPressed: _reloadEntries, // Neuladen der Daten
child: const Text('Neu laden'),
),
],
),
);
}
}
import 'package:flutter/material.dart';
class CommonFunctions {
static Future<void> showNewDialog(BuildContext context) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const AlertDialog(
title: Text("My Dialog"),
content: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: TextField(),
));
},
);
}
}
My issue is: When I open a dialog with the help of showNewDialog I get a dialog with a TextField. When I focus (or defocus) the textfield the SideBar is rebuilded. Only the Sidebar. Means that I see the output of print("I AM CALLED"); from my SideBar many times but I do not see the output of print("ME TOO"); from my BaseLayout.
I do not understand why my SideBar is rebuilded when focusing a textfield in the dialog. Especially because I am using the Consumer pattern to only rebuild the view when I call the notify method.
Does anyone have an idea why my view keeps rebuilding ?
------------ EDIT ------------ The SideBar
The Dialog
When I click in the TextField the focus is set and the KeyBoard is shown which triggers the rebuild (multiple times).
Log output when Opening dialog and seting focus
D/InputMethodManager(15863): showSoftInput() view=io.flutter.embedding.android.FlutterView{d6fdf5d VFE...... .F...... 0,0-1080,2296 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
I/AssistStructure(15863): Flattened final assist data: 384 bytes, containing 1 windows, 3 views
D/EGL_emulation(15863): app_time_stats: avg=195.49ms min=2.51ms max=2614.66ms count=14
D/InsetsController(15863): show(ime(), fromIme=true)
D/EGL_emulation(15863): app_time_stats: avg=483.08ms min=1.31ms max=9613.11ms count=20
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.461095: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.483933: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.527776: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.557668: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.603329: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.603692: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.604326: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.604595: Notifiying listeners that entries list have changed
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.614896: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.646553: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.689545: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.726604: Get Entries from REST endpoint
I/flutter (15863): I AM CALLED
I/flutter (15863): [INFO] 2024-04-23 07:49:07.761659: Get Entries from REST endpoint
I/flutter (15863): [INFO] 2024-04-23 07:49:07.814218: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.814865: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.855985: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.856984: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.869618: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.871666: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.886194: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.886997: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.909218: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.910125: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.939734: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.940123: Notifiying listeners that entrties list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.941196: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.941953: Notifiying listeners that entries list have changed
I/flutter (15863): [INFO] 2024-04-23 07:49:07.955544: Start sorting entries
I/flutter (15863): [INFO] 2024-04-23 07:49:07.956654: Notifiying listeners that entries list have changed
D/EGL_emulation(15863): app_time_stats: avg=32.01ms min=3.19ms max=282.24ms count=25
D/EGL_emulation(15863): app_time_stats: avg=493.36ms min=483.87ms max=500.10ms count=3
D/EGL_emulation(15863): app_time_stats: avg=510.94ms min=501.94ms max=519.93ms count=2
D/EGL_emulation(15863): app_time_stats: avg=497.79ms min=492.59ms max=500.79ms count=3
D/EGL_emulation(15863): app_time_stats: avg=504.19ms min=500.70ms max=507.68ms count=2
D/EGL_emulation(15863): app_time_stats: avg=496.64ms min=492.22ms max=501.38ms count=3
D/EGL_emulation(15863): app_time_stats: avg=500.09ms min=482.98ms max=515.92ms count=3
D/EGL_emulation(15863): app_time_stats: avg=500.91ms min=484.92ms max=516.91ms count=2
Similar behaviour can be observed when defocusing the Dialog.It does not happen if I click on a Button in the Dialog or when I am inputing data into the TextField. Only when focusing/defocusing the TextField.
In case that someone is facing a similar issue: In my case I had to remove the return ResponsiveBuilder(...)
in my HomeMobileView
. I am not 100% sure why it is responsible to rebuild the whole UI every time (even if only the keyboard is displayed).