Flutter Dialog 无意中触发了后面布局的重新创建

问题描述 投票:0回答:1

我的 FLutter 代码有一个问题,我无法解决,也无法理解为什么会发生这种情况。

首先是问题中的代码:

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(),
            ));
      },
    );
  }
}

我的问题是:当我在 showNewDialog 的帮助下打开对话框时,我得到一个带有 TextField 的对话框。当我聚焦(或散焦)文本字段时,侧边栏将被重建。只有侧边栏。意味着我看到 print("I AM CALLED"); 的输出来自我的侧边栏很多次,但我没有看到 print("ME TOO"); 的输出来自我的 BaseLayout。

我不明白为什么当聚焦对话框中的文本字段时我的侧边栏会被重建。特别是因为我使用消费者模式仅在调用通知方法时重建视图。

有人知道为什么我的视图不断重建吗?

------------ 编辑 ------------ 侧边栏

对话框

当我单击文本字段时,焦点被设置并显示键盘,这会触发重建(多次)。

打开对话框并设置焦点时的日志输出

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

在对话框散焦时可以观察到类似的行为。如果我单击对话框中的按钮或在文本字段中输入数据,则不会发生这种情况。仅当文本字段聚焦/散焦时。

flutter dart responsive consumer
1个回答
0
投票

如果有人面临类似的问题:就我而言,我必须删除 HomeMobileView 中的 return ResponsiveBuilder(...) 。我不是 100% 确定为什么它每次都要重建整个 UI(即使只显示键盘)。

© www.soinside.com 2019 - 2024. All rights reserved.