最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

flutter - Causing unnecessary rebuilds - Stack Overflow

programmeradmin1浏览0评论

The thing is I was following a Youtube to tutorial to create a note app. I am not following the course exactly. Mainly implementing the same functionality but with an a lot better UI.

Here is my code which has problem that when ever I press any of my textfield whether title or text a rebuild is triggered. I don't understand why. The tutorial code also uses the same structure

// My code 
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mynotes/constants/colors/app_colors.dart';
import 'package:mynotes/constants/widgets/helper_text.dart';
import 'package:mynotes/services/auth/auth_services.dart';

import 'dart:developer' as devtools show log;

import 'package:mynotes/services/crud/notes_services.dart';
import 'package:mynotes/utils/show_loading_dialog.dart';

class AddNoteView extends StatefulWidget {
  const AddNoteView({super.key});

  @override
  State<AddNoteView> createState() => _AddNoteViewState();
}

class _AddNoteViewState extends State<AddNoteView> {
  DatabaseNote? _note;
  late final NotesService _notesService;
  late FocusNode _titleFocus;
  late FocusNode _textFocus;
  late TextEditingController _titleController;
  late TextEditingController _textController;
  

  void _deleteNoteIfTextIsEmpty() {
    final note = _note;
    if (note != null &&
        _textController.text.isEmpty &&
        _titleController.text.isEmpty) {
      _notesService.deleteNote(noteId: note.id);
    }
  }

  void _saveNoteIfTextIfTextIsNotEmpty() async {
    final note = _note;

    if (note != null && _textController.text.isNotEmpty ||
        _titleController.text.isNotEmpty) {
      final text = '${_titleController.text}&${_textController.text}';
      await _notesService.updateNote(note: note!, text: text);
    }
  }

  Future<DatabaseNote> createNewNote() async {
    final existingNote = _note;
    if (existingNote != null) {
      return existingNote;
    }
    final currentUser = AuthServices.firebase().currentUser!;
    final email = currentUser.email!;
    final owner = await _notesService.getUser(email: email);
    return await _notesService.createNote(
      owner: owner,
      text: '',
    );
  }

  void _textControllerListener() async {
    final note = _note;
    if (note == null) {
      return;
    }
    final String text = '${_titleController.text}&${_textController.text}';
    await _notesService.updateNote(
      note: note,
      text: text,
    );
  }

  void _setupTextControllerListener() {
    _titleController.removeListener(_textControllerListener);
    _titleController.addListener(_textControllerListener);
    _textController.removeListener(_textControllerListener);
    _textController.addListener(_textControllerListener);
  }

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(milliseconds: 300), () {
      if (!context.mounted) {
        return;
      }
      FocusScope.of(context).requestFocus(_titleFocus);
    });

    
    _notesService = NotesService.singlton();
    _titleFocus = FocusNode();
    _textFocus = FocusNode();
    _titleController = TextEditingController();
    _textController = TextEditingController();
  }

  @override
  void dispose() {
    _deleteNoteIfTextIsEmpty();
    _saveNoteIfTextIfTextIsNotEmpty();
    _titleFocus.dispose();
    _textFocus.dispose();
    _titleController.dispose();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.softWhite,
      appBar: AppBar(
        backgroundColor: AppColors.softWhite,
        elevation: 0,
        scrolledUnderElevation: 0,
        centerTitle: false,
        automaticallyImplyLeading: false,
        title: Transform.translate(
          offset: Offset(-16, 0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.min,
            children: [
              GestureDetector(
                onTap: () => Navigator.of(context).pop(),
                child: Stack(
                  children: [
                    Icon(
                      Icons.arrow_back_ios_new_rounded,
                      size: 26,
                      color: AppColors.jetBlack,
                    ),
                    Positioned(
                      left: 1,
                      child: Icon(
                        Icons.arrow_back_ios_new_rounded,
                        size: 26,
                        color: AppColors.jetBlack,
                      ),
                    ),
                  ],
                ),
              ),
              HelperText(
                text: 'Notes',
                textColor: AppColors.jetBlack,
                fontSize: 18,
                fontWeight: FontWeight.w600,
              ),
            ],
          ),
        ),
        actions: [
          IconButton(
            icon:
                Icon(Icons.favorite_border_rounded, color: AppColors.jetBlack),
            onPressed: () {},
          ),
          IconButton(
            icon: Icon(Icons.save_alt_rounded, color: AppColors.jetBlack),
            onPressed: () {},
          ),
        ],
      ),
      body: FutureBuilder(
          future: createNewNote(),
          builder: (conext, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.done:
                _note = snapshot.data as DatabaseNote;
                _setupTextControllerListener();
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 15),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      TextField(
                        controller: _titleController,
                        focusNode: _titleFocus,
                        textInputAction: TextInputAction.next,
                        keyboardType: TextInputType.multiline,
                        onSubmitted: (_) =>
                            FocusScope.of(context).requestFocus(_textFocus),
                        style: GoogleFonts.lobster(
                          fontSize: 34,
                          // Larger size for title
                          fontWeight: FontWeight.bold,
                          color: AppColors.jetBlack,
                        ),
                        decoration: InputDecoration(
                          hintText: "Title",
                          hintStyle: TextStyle(
                            fontSize: 34,
                            fontWeight: FontWeight.normal,
                            color:
                                Colors.grey.shade400, // Light grey hint color
                          ),
                          border: InputBorder.none,
                        ),
                        minLines: 1,
                        maxLines: 2, // Limit to two lines like Apple's Notes
                      ),
                      // Spacing between title and body
                      Expanded(
                        child: TextField(
                          controller: _textController,
                          focusNode: _textFocus,
                          textInputAction: TextInputAction.newline,
                          style: TextStyle(
                            fontSize: 18,
                            color: AppColors.jetBlack,
                            height: 1.5, // Increase line height for readability
                          ),
                          decoration: InputDecoration(
                            hintText: "Write your note here...",
                            hintStyle: TextStyle(
                              fontSize: 18,
                              color:
                                  Colors.grey.shade400, // Light grey hint color
                            ),
                            border: InputBorder.none,
                          ),
                          minLines: 1,
                          maxLines: null,
                          keyboardType: TextInputType.multiline,
                        ),
                      ),
                    ],
                  ),
                );

              default:
                return showLoadingDialog();
            }
          }),
    );
  }
}

//Tutorial Code 

import 'package:flutter/material.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/services/crud/notes_service.dart';

class NewNoteView extends StatefulWidget {
  const NewNoteView({Key? key}) : super(key: key);

  @override
  _NewNoteViewState createState() => _NewNoteViewState();
}

class _NewNoteViewState extends State<NewNoteView> {
  DatabaseNote? _note;
  late final NotesService _notesService;
  late final TextEditingController _textController;

  @override
  void initState() {
    _notesService = NotesService();
    _textController = TextEditingController();
    super.initState();
  }

  void _textControllerListener() async {
    final note = _note;
    if (note == null) {
      return;
    }
    final text = _textController.text;
    await _notesService.updateNote(
      note: note,
      text: text,
    );
  }

  void _setupTextControllerListener() {
    _textController.removeListener(_textControllerListener);
    _textController.addListener(_textControllerListener);
  }

  Future<DatabaseNote> createNewNote() async {
    final existingNote = _note;
    if (existingNote != null) {
      return existingNote;
    }
    final currentUser = AuthService.firebase().currentUser!;
    final email = currentUser.email!;
    final owner = await _notesService.getUser(email: email);
    return await _notesService.createNote(owner: owner);
  }

  void _deleteNoteIfTextIsEmpty() {
    final note = _note;
    if (_textController.text.isEmpty && note != null) {
      _notesService.deleteNote(id: note.id);
    }
  }

  void _saveNoteIfTextNotEmpty() async {
    final note = _note;
    final text = _textController.text;
    if (note != null && text.isNotEmpty) {
      await _notesService.updateNote(
        note: note,
        text: text,
      );
    }
  }

  @override
  void dispose() {
    _deleteNoteIfTextIsEmpty();
    _saveNoteIfTextNotEmpty();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('New Note'),
      ),
      body: FutureBuilder(
        future: createNewNote(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.done:
              _note = snapshot.data as DatabaseNote;
              _setupTextControllerListener();
              return TextField(
                controller: _textController,
                keyboardType: TextInputType.multiline,
                maxLines: null,
                decoration: const InputDecoration(
                  hintText: 'Start typing your note...',
                ),
              );
            default:
              return const CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

But it's not causing any problems I can't understand why

The thing is I was following a Youtube to tutorial to create a note app. I am not following the course exactly. Mainly implementing the same functionality but with an a lot better UI.

Here is my code which has problem that when ever I press any of my textfield whether title or text a rebuild is triggered. I don't understand why. The tutorial code also uses the same structure

// My code 
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mynotes/constants/colors/app_colors.dart';
import 'package:mynotes/constants/widgets/helper_text.dart';
import 'package:mynotes/services/auth/auth_services.dart';

import 'dart:developer' as devtools show log;

import 'package:mynotes/services/crud/notes_services.dart';
import 'package:mynotes/utils/show_loading_dialog.dart';

class AddNoteView extends StatefulWidget {
  const AddNoteView({super.key});

  @override
  State<AddNoteView> createState() => _AddNoteViewState();
}

class _AddNoteViewState extends State<AddNoteView> {
  DatabaseNote? _note;
  late final NotesService _notesService;
  late FocusNode _titleFocus;
  late FocusNode _textFocus;
  late TextEditingController _titleController;
  late TextEditingController _textController;
  

  void _deleteNoteIfTextIsEmpty() {
    final note = _note;
    if (note != null &&
        _textController.text.isEmpty &&
        _titleController.text.isEmpty) {
      _notesService.deleteNote(noteId: note.id);
    }
  }

  void _saveNoteIfTextIfTextIsNotEmpty() async {
    final note = _note;

    if (note != null && _textController.text.isNotEmpty ||
        _titleController.text.isNotEmpty) {
      final text = '${_titleController.text}&${_textController.text}';
      await _notesService.updateNote(note: note!, text: text);
    }
  }

  Future<DatabaseNote> createNewNote() async {
    final existingNote = _note;
    if (existingNote != null) {
      return existingNote;
    }
    final currentUser = AuthServices.firebase().currentUser!;
    final email = currentUser.email!;
    final owner = await _notesService.getUser(email: email);
    return await _notesService.createNote(
      owner: owner,
      text: '',
    );
  }

  void _textControllerListener() async {
    final note = _note;
    if (note == null) {
      return;
    }
    final String text = '${_titleController.text}&${_textController.text}';
    await _notesService.updateNote(
      note: note,
      text: text,
    );
  }

  void _setupTextControllerListener() {
    _titleController.removeListener(_textControllerListener);
    _titleController.addListener(_textControllerListener);
    _textController.removeListener(_textControllerListener);
    _textController.addListener(_textControllerListener);
  }

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(milliseconds: 300), () {
      if (!context.mounted) {
        return;
      }
      FocusScope.of(context).requestFocus(_titleFocus);
    });

    
    _notesService = NotesService.singlton();
    _titleFocus = FocusNode();
    _textFocus = FocusNode();
    _titleController = TextEditingController();
    _textController = TextEditingController();
  }

  @override
  void dispose() {
    _deleteNoteIfTextIsEmpty();
    _saveNoteIfTextIfTextIsNotEmpty();
    _titleFocus.dispose();
    _textFocus.dispose();
    _titleController.dispose();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.softWhite,
      appBar: AppBar(
        backgroundColor: AppColors.softWhite,
        elevation: 0,
        scrolledUnderElevation: 0,
        centerTitle: false,
        automaticallyImplyLeading: false,
        title: Transform.translate(
          offset: Offset(-16, 0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.min,
            children: [
              GestureDetector(
                onTap: () => Navigator.of(context).pop(),
                child: Stack(
                  children: [
                    Icon(
                      Icons.arrow_back_ios_new_rounded,
                      size: 26,
                      color: AppColors.jetBlack,
                    ),
                    Positioned(
                      left: 1,
                      child: Icon(
                        Icons.arrow_back_ios_new_rounded,
                        size: 26,
                        color: AppColors.jetBlack,
                      ),
                    ),
                  ],
                ),
              ),
              HelperText(
                text: 'Notes',
                textColor: AppColors.jetBlack,
                fontSize: 18,
                fontWeight: FontWeight.w600,
              ),
            ],
          ),
        ),
        actions: [
          IconButton(
            icon:
                Icon(Icons.favorite_border_rounded, color: AppColors.jetBlack),
            onPressed: () {},
          ),
          IconButton(
            icon: Icon(Icons.save_alt_rounded, color: AppColors.jetBlack),
            onPressed: () {},
          ),
        ],
      ),
      body: FutureBuilder(
          future: createNewNote(),
          builder: (conext, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.done:
                _note = snapshot.data as DatabaseNote;
                _setupTextControllerListener();
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 15),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      TextField(
                        controller: _titleController,
                        focusNode: _titleFocus,
                        textInputAction: TextInputAction.next,
                        keyboardType: TextInputType.multiline,
                        onSubmitted: (_) =>
                            FocusScope.of(context).requestFocus(_textFocus),
                        style: GoogleFonts.lobster(
                          fontSize: 34,
                          // Larger size for title
                          fontWeight: FontWeight.bold,
                          color: AppColors.jetBlack,
                        ),
                        decoration: InputDecoration(
                          hintText: "Title",
                          hintStyle: TextStyle(
                            fontSize: 34,
                            fontWeight: FontWeight.normal,
                            color:
                                Colors.grey.shade400, // Light grey hint color
                          ),
                          border: InputBorder.none,
                        ),
                        minLines: 1,
                        maxLines: 2, // Limit to two lines like Apple's Notes
                      ),
                      // Spacing between title and body
                      Expanded(
                        child: TextField(
                          controller: _textController,
                          focusNode: _textFocus,
                          textInputAction: TextInputAction.newline,
                          style: TextStyle(
                            fontSize: 18,
                            color: AppColors.jetBlack,
                            height: 1.5, // Increase line height for readability
                          ),
                          decoration: InputDecoration(
                            hintText: "Write your note here...",
                            hintStyle: TextStyle(
                              fontSize: 18,
                              color:
                                  Colors.grey.shade400, // Light grey hint color
                            ),
                            border: InputBorder.none,
                          ),
                          minLines: 1,
                          maxLines: null,
                          keyboardType: TextInputType.multiline,
                        ),
                      ),
                    ],
                  ),
                );

              default:
                return showLoadingDialog();
            }
          }),
    );
  }
}

//Tutorial Code 

import 'package:flutter/material.dart';
import 'package:mynotes/services/auth/auth_service.dart';
import 'package:mynotes/services/crud/notes_service.dart';

class NewNoteView extends StatefulWidget {
  const NewNoteView({Key? key}) : super(key: key);

  @override
  _NewNoteViewState createState() => _NewNoteViewState();
}

class _NewNoteViewState extends State<NewNoteView> {
  DatabaseNote? _note;
  late final NotesService _notesService;
  late final TextEditingController _textController;

  @override
  void initState() {
    _notesService = NotesService();
    _textController = TextEditingController();
    super.initState();
  }

  void _textControllerListener() async {
    final note = _note;
    if (note == null) {
      return;
    }
    final text = _textController.text;
    await _notesService.updateNote(
      note: note,
      text: text,
    );
  }

  void _setupTextControllerListener() {
    _textController.removeListener(_textControllerListener);
    _textController.addListener(_textControllerListener);
  }

  Future<DatabaseNote> createNewNote() async {
    final existingNote = _note;
    if (existingNote != null) {
      return existingNote;
    }
    final currentUser = AuthService.firebase().currentUser!;
    final email = currentUser.email!;
    final owner = await _notesService.getUser(email: email);
    return await _notesService.createNote(owner: owner);
  }

  void _deleteNoteIfTextIsEmpty() {
    final note = _note;
    if (_textController.text.isEmpty && note != null) {
      _notesService.deleteNote(id: note.id);
    }
  }

  void _saveNoteIfTextNotEmpty() async {
    final note = _note;
    final text = _textController.text;
    if (note != null && text.isNotEmpty) {
      await _notesService.updateNote(
        note: note,
        text: text,
      );
    }
  }

  @override
  void dispose() {
    _deleteNoteIfTextIsEmpty();
    _saveNoteIfTextNotEmpty();
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('New Note'),
      ),
      body: FutureBuilder(
        future: createNewNote(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.done:
              _note = snapshot.data as DatabaseNote;
              _setupTextControllerListener();
              return TextField(
                controller: _textController,
                keyboardType: TextInputType.multiline,
                maxLines: null,
                decoration: const InputDecoration(
                  hintText: 'Start typing your note...',
                ),
              );
            default:
              return const CircularProgressIndicator();
          }
        },
      ),
    );
  }
}

But it's not causing any problems I can't understand why

Share Improve this question edited Feb 8 at 7:37 marc_s 755k184 gold badges1.4k silver badges1.5k bronze badges asked Feb 8 at 7:35 Mujtaba FarhanMujtaba Farhan 13 bronze badges 1
  • this is because _textControllerListener is called every time you click on your text fields – pskink Commented Feb 8 at 9:22
Add a comment  | 

1 Answer 1

Reset to default 1

Instead of passing the Future createNewNote() to future inside the build method (which recreates the Future on every rebuild), create and manage the Future in a StatefulWidget.

Future<DatabaseNote>? _createdNewNote;

Then initalize this property inside the initState methode

 @override
 void initState() {
   _createdNewNote = createNewNote;
   super.initState();
}

Then Pass the _createdNewNote to the future of the FutureBuilder like :

future: _createdNewNote,

By initializing the Future in initState, you ensure it is only created once and doesn't change on rebuilds.

发布评论

评论列表(0)

  1. 暂无评论