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 |1 Answer
Reset to default 1Instead 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.
_textControllerListener
is called every time you click on your text fields – pskink Commented Feb 8 at 9:22