I'm trying to display a scaffold msg when user inputted a value greater than some other value...
It is working fine until I clear the input...
After clearing the input it throws an exception...
Here it is...
══╡ EXCEPTION CAUGHT BY WIDGETS ╞═══════════════════════════════════════════════════════════════════
The following FormatException was thrown while calling onChanged:
Invalid number (at character 1)
^
When the exception was thrown, this was the stack:
#0 int._handleFormatError (dart:core-patch/integers_patch.dart:129:7)
#1 int.parse (dart:core-patch/integers_patch.dart:55:14)
#2 _GeneralTabState.build.<anonymous closure>.<anonymous closure>
(package:shop_app_vendor/screens/add_products/general_tab.dart:247:25)
#3 new TextFormField.<anonymous closure>.onChangedHandler (package:flutter/src/material/text_form_field.dart:188:25)
#4 EditableTextState._formatAndSetValue (package:flutter/src/widgets/editable_text.dart:2630:27)
#5 EditableTextState.userUpdateTextEditingValue (package:flutter/src/widgets/editable_text.dart:2919:5)
#6 EditableTextState._replaceText (package:flutter/src/widgets/editable_text.dart:3144:5)
#7 CallbackAction.invoke (package:flutter/src/widgets/actions.dart:534:39)
#8 ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:573:21)
#9 Actions.invoke.<anonymous closure> (package:flutter/src/widgets/actions.dart:872:48)
#10 Actions._visitActionsAncestors (package:flutter/src/widgets/actions.dart:653:18)
#11 Actions.invoke (package:flutter/src/widgets/actions.dart:866:30)
#12 _DeleteTextAction.invoke (package:flutter/src/widgets/editable_text.dart:4042:20)
#13 _OverridableContextAction.invokeDefaultAction (package:flutter/src/widgets/actions.dart:1696:28)
#14 _OverridableActionMixin.invoke (package:flutter/src/widgets/actions.dart:1559:9)
#15 ActionDispatcher.invokeAction (package:flutter/src/widgets/actions.dart:571:21)
#16 ShortcutManager.handleKeypress (package:flutter/src/widgets/shortcuts.dart:755:38)
#17 _ShortcutsState._handleOnKey (package:flutter/src/widgets/shortcuts.dart:956:20)
#18 FocusManager._handleKeyMessage (package:flutter/src/widgets/focus_manager.dart:1687:32)
#19 KeyEventManager._dispatchKeyMessage (package:flutter/src/services/hardware_keyboard.dart:828:34)
#20 KeyEventManager.handleRawKeyMessage (package:flutter/src/services/hardware_keyboard.dart:875:15)
#21 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:77:49)
#22 BasicMessageChannel.setMessageHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:76:47)
#23 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:380:35)
#24 _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:377:46)
#25 _invoke2.<anonymous closure> (dart:ui/hooks.dart:190:15)
#29 _invoke2 (dart:ui/hooks.dart:189:10)
#30 _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:42:5)
#31 _Channel.push (dart:ui/channel_buffers.dart:132:31)
#32 ChannelBuffers.push (dart:ui/channel_buffers.dart:329:17)
#33 PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:589:22)
#34 _dispatchPlatformMessage (dart:ui/hooks.dart:89:31)
(elided 3 frames from dart:async)
════════════════════════════════════════════════════════════════════════════════════════════════════
And here's my code...
scf.dart
class SCF {
scaffoldMsg({context, msg}) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(msg),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'OK',
textColor: Colors.white,
onPressed: () {
ScaffoldMessenger.of(context).clearSnackBars();
},
),
));
}
}
general_tab.dart
class GeneralTab extends StatefulWidget {
const GeneralTab({Key? key}) : super(key: key);
#override
State<GeneralTab> createState() => _GeneralTabState();
}
class _GeneralTabState extends State<GeneralTab> {
final SCF _scf = SCF();
#override
Widget build(BuildContext context) {
return Consumer<ProductProvider>(
builder: (context, provider, child) {
return ListView(
padding: const EdgeInsets.all(15.0),
children: [
// Regular Price
FormFieldInput(
label: 'Regular Price',
inputType: TextInputType.number,
onChanged: (value) {
provider.getFormData(regularPrice: int.parse(value));
},
),
// Sales Price
FormFieldInput(
label: 'Sales Price',
inputType: TextInputType.number,
onChanged: (value) {
if (int.parse(value) > provider.productData!['regularPrice']) {
_scf.scaffoldMsg(
context: context,
msg: 'Sales price should be less than regular price',
);
}
setState(() {
provider.getFormData(salesPrice: int.parse(value));
});
},
),
],
);
},
);
}
}
product_provider.dart
class ProductProvider with ChangeNotifier {
Map<String, dynamic>? productData = {};
getFormData({
int? regularPrice,
int? salesPrice,
}) {
if (regularPrice != null) {
productData!['regularPrice'] = regularPrice;
}
if (salesPrice != null) {
productData!['salesPrice'] = salesPrice;
}
notifyListeners();
}
}
form_field.dart
class FormFieldInput extends StatelessWidget {
final String? label;
final void Function(String)? onChanged;
final TextInputType? inputType;
const FormFieldInput({
Key? key,
this.label,
this.onChanged,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
label: Text(label!),
),
onChanged: onChanged,
);
}
}
This is working fine and showing scaffold msg until I delete the content in the input...
So how can I fix this?
that's because when the the text is empty the app cast the empty to number so this error happened when you reach the compare , so you need to check if the text is not empty before you trigger the compare.
so in your case in the onChange function before trigger any logic ,make sure that the value in the text field is not null or empty.
I found a solution.
By adding if(value.isNotEmpty) to the on change error is gone...
Need to check whether value is empty or not.
That's how I solved my problem.
Thank you
general_tab.dart
class GeneralTab extends StatefulWidget {
const GeneralTab({Key? key}) : super(key: key);
#override
State<GeneralTab> createState() => _GeneralTabState();
}
class _GeneralTabState extends State<GeneralTab> {
final SCF _scf = SCF();
#override
Widget build(BuildContext context) {
return Consumer<ProductProvider>(
builder: (context, provider, child) {
return ListView(
padding: const EdgeInsets.all(15.0),
children: [
// Regular Price
FormFieldInput(
label: 'Regular Price',
inputType: TextInputType.number,
onChanged: (value) {
if (value.isNotEmpty) {
provider.getFormData(regularPrice: int.parse(value));
}
},
),
// Sales Price
FormFieldInput(
label: 'Sales Price',
inputType: TextInputType.number,
onChanged: (value) {
if (value.isNotEmpty) {
if (int.parse(value) > provider.productData!['regularPrice']) {
_scf.scaffoldMsg(
context: context,
msg: 'Sales price should be less than regular price',
);
}
}
setState(() {
provider.getFormData(salesPrice: int.parse(value));
});
},
),
],
);
},
);
}
}
Related
I added a new field to the class CloudNote, now I am getting an error!
I am getting the error when the app is trying to display a list.
Here is all my code without adding the field :: https://github.com/casas1010/flutter_firebase_vendor_management
I know its a simple issue, but I have tried to troubleshoot this for like an hour and have not made any progress
CloudNote class::
import 'package:cloud_firestore/cloud_firestore.dart';
import '/services/cloud/cloud_storage_constants.dart';
import 'package:flutter/foundation.dart';
/*
https://youtu.be/VPvVD8t02U8?t=87934
*/
#immutable
class CloudNote {
final String documentId;
final String jobCreatorId;
final String jobDescription;
final String jobState; // I added this
const CloudNote({
required this.documentId,
required this.jobCreatorId,
required this.jobDescription,
required this.jobState, // I added this
});
// acts as constructor
CloudNote.fromSnapshot(QueryDocumentSnapshot<Map<String, dynamic>> snapshot)
: documentId = snapshot.id,
jobCreatorId = snapshot.data()[jobCreatorIdColumn],
jobState = snapshot.data()[jobStateColumn], // I added this
jobDescription = snapshot.data()[jobDescriptionColumn] as String;
}
notes view ::
import 'package:flutter/material.dart';
import '/constants/routes.dart';
import '/enums/menu_action.dart';
import '/services/auth/auth_service.dart';
import '/services/cloud/cloud_note.dart';
import '/services/cloud/firebase_cloud_storage.dart';
import '/utilities/dialogs/logout_dialog.dart';
import '/views/notes/notes_list_view.dart';
class NotesView extends StatefulWidget {
const NotesView({Key? key}) : super(key: key);
#override
_NotesViewState createState() => _NotesViewState();
}
class _NotesViewState extends State<NotesView> {
late final FirebaseCloudStorage _notesService;
String get userId => AuthService.firebase().currentUser!.id;
#override
void initState() {
_notesService = FirebaseCloudStorage();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Your jobs'),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed(createOrUpdateNoteRoute);
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuAction>(
onSelected: (value) async {
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
if (shouldLogout) {
await AuthService.firebase().logOut();
Navigator.of(context).pushNamedAndRemoveUntil(
loginRoute,
(_) => false,
);
}
}
},
itemBuilder: (context) {
return const [
PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Log out'),
),
];
},
)
],
),
body: StreamBuilder(
stream: _notesService.allNotes(jobCreatorId: userId),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allNotes = snapshot.data as Iterable<CloudNote>;
return NotesListView(
notes: allNotes,
onDeleteNote: (note) async {
await _notesService.deleteNote(documentId: note.documentId);
},
onTap: (note) {
Navigator.of(context).pushNamed(
createOrUpdateNoteRoute,
arguments: note,
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
),
);
}
}
Error
The following _TypeError was thrown building NotesListView(dirty):
type 'Null' is not a subtype of type 'String'
The relevant error-causing widget was
NotesListView
lib/…/notes/notes_view.dart:72
When the exception was thrown, this was the stack
#0 new CloudNote.fromSnapshot
package:ijob_clone_app/…/cloud/cloud_note.dart:26
#1 FirebaseCloudStorage.allNotes.<anonymous closure>.<anonymous closure>
package:ijob_clone_app/…/cloud/firebase_cloud_storage.dart:39
#2 MappedListIterable.elementAt (dart:_internal/iterable.dart:413:31)
#3 ListIterator.moveNext (dart:_internal/iterable.dart:342:26)
#4 WhereIterator.moveNext (dart:_internal/iterable.dart:438:22)
#5 Iterable.length (dart:core/iterable.dart:497:15)
#6 NotesListView.build
package:ijob_clone_app/…/notes/notes_list_view.dart:26
#7 StatelessElement.build
package:flutter/…/widgets/framework.dart:4949
#8 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4878
#9 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#10 ComponentElement._firstBuild
package:flutter/…/widgets/framework.dart:4859
#11 ComponentElement.mount
package:flutter/…/widgets/framework.dart:4853
#12 Element.inflateWidget
package:flutter/…/widgets/framework.dart:3863
#13 Element.updateChild
package:flutter/…/widgets/framework.dart:3586
#14 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#15 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#16 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#17 BuildOwner.buildScope
package:flutter/…/widgets/framework.dart:2667
#18 WidgetsBinding.drawFrame
package:flutter/…/widgets/binding.dart:882
#19 RendererBinding._handlePersistentFrameCallback
package:flutter/…/rendering/binding.dart:378
#20 SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1175
#21 SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1104
#22 SchedulerBinding._handleDrawFrame
package:flutter/…/scheduler/binding.dart:1015
#23 _invoke (dart:ui/hooks.dart:148:13)
#24 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#25 _drawFrame (dart:ui/hooks.dart:115:31)
════════════════════════════════════════════════════════════════════════════════
NotesListView ::
import 'package:flutter/material.dart';
import '/services/cloud/cloud_note.dart';
import '/utilities/dialogs/delete_dialog.dart';
/*
source: https://www.youtube.com/watch?v=VPvVD8t02U8&t=59608s
class creation :: 22:02:54
*/
typedef NoteCallback = void Function(CloudNote note);
class NotesListView extends StatelessWidget {
final Iterable<CloudNote> notes; // list of notes
final NoteCallback onDeleteNote;
final NoteCallback onTap;
const NotesListView({
Key? key,
required this.notes,
required this.onDeleteNote,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
final note =
notes.elementAt(index); // current note whose data we are returning
return ListTile(
onTap: () {
onTap(note);
},
title: Text(
note.jobDescription,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
onPressed: () async {
final shouldDelete = await showDeleteDialog(context);
if (shouldDelete) {
onDeleteNote(note);
}
},
icon: const Icon(Icons.delete),
),
);
},
);
}
}
This happens if you forget to update your Firestore entries. Atleast one of your CloudNote entries in Firestore does not have the field jobState. That's why Firestore returns a Null value. But it tries to map to String which leads to an exception.
Make sure to rerun the project.
or
flutter clean
and then
flutter run
I have tried everything I know But Not been able to solve the issue
RUN Output :
Launching lib\main.dart on moto g 40 fusion in debug mode...
Running Gradle task 'assembleDebug'...
√ Built build\app\outputs\flutter-apk\app-debug.apk.
D/FlutterLocationService(30742): Creating service.
D/FlutterLocationService(30742): Binding to location service.
Debug service listening on ws://127.0.0.1:51746/KGrWp8utBGI=/ws
Syncing files to device moto g 40 fusion...
E/SQLiteLog(30742): (1) no such table: placestore in "SELECT * FROM placestore"
I/AssistStructure(30742): Flattened final assist data: 396 bytes, containing 1 windows, 3 views
D/MediaScannerConnection(30742): Scanned /data/user/0/com.example.memory_place/cache/04c048c7-d58f-497c-b050-fb56e943fc1b615751689670497232.mp4 to null
D/ThumbnailPlugin(30742): original w:1080, h:1920 => 150, 267
E/flutter (30742): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: FileSystemException: Cannot copy file to '/data/user/0/com.example.memory_place/app_flutter/�$��� 5�5f��O ���
======== Exception caught by gesture ===============================================================
The following LateError was thrown while handling a gesture:
LateInitializationError: Field '_CurrentThumbnail#643317077' has not been initialized.
When the exception was thrown, this was the stack:
#0 AddScreen._CurrentThumbnail (package:memory_place/Screens/add_places_screen.dart)
#1 AddScreen.build.SafePlace (package:memory_place/Screens/add_places_screen.dart:25:47)
#2 AddScreen.build. (package:memory_place/Screens/add_places_screen.dart:64:15)
#3 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
#5 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:608:11)
#6 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:296:5)
#7 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:230:7)
#8 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:563:9)
#9 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:94:12)
#10 PointerRouter._dispatchEventToRoutes. (package:flutter/src/gestures/pointer_router.dart:139:9)
#11 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#12 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:137:18)
#13 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:123:7)
#14 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#15 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#16 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:322:11)
#17 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#18 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#19 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:296:7)
#20 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:279:7)
#24 _invoke1 (dart:ui/hooks.dart:170:10)
#25 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#26 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)
Handler: "onTap"
Recognizer: TapGestureRecognizer#12cd2
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(239.2, 911.2)
finalLocalPosition: Offset(239.2, 11.2)
button: 1
sent tap down
DB HELPER FILE:
import 'package:sqflite/sqflite.dart' as sql;
import 'package:path/path.dart' as p;
class dbHelper{
static Future<sql.Database> DataBase() async{
return sql.openDatabase(p.join(await sql.getDatabasesPath(),'memory_place.db'),
onCreate: (db, version) async{
return await db.execute('CREATE TABLE placestore(id TEXT PRIMARY KEY, title TEXT, video TEXT, thumbnail TEXT)');
},version: 1);
}
static Future<void> insert(String Table,Map<String,Object> data) async{
final db = await dbHelper.DataBase();
db.insert(
Table,
data,
conflictAlgorithm: sql.ConflictAlgorithm.replace);
}
static Future<List<Map<String,dynamic>>> getData(String table) async{
final db = await dbHelper.DataBase();
return db.query(table);
}
}
IMAGE INPUT PAGE :
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart' as syspath;
import 'package:path/path.dart' as p;
import 'package:video_thumbnail/video_thumbnail.dart';
class ImageInput extends StatefulWidget {
Function onSelected;
ImageInput({required this.onSelected});
#override
State<ImageInput> createState() => _ImageInputState();
}
class _ImageInputState extends State<ImageInput> {
XFile ? _referenceXFileVid;
File? _referenceVidFile;
Future<void> TakePic() async{
final _imagePicker = ImagePicker();
final NewFile = await _imagePicker.pickVideo(
source: ImageSource.camera,
maxDuration: Duration(minutes: 1));
setState(() {
_referenceVidFile = File(NewFile!.path);
_referenceXFileVid = NewFile;
});
}
Future<Uint8List?> VideoThumbNail(XFile Video) async{
final uint8list = await VideoThumbnail.thumbnailData(
video: Video.path,
imageFormat: ImageFormat.JPEG,
maxWidth: 150,
quality: 25,
);
return await uint8list;
}
#override
Widget build(BuildContext context) {
return Row(
children: [
Container(
height: 267,
width: 150,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(width: 1,color: Colors.grey),
),
child: _referenceVidFile!= null ?
FutureBuilder<Uint8List?>(
future: VideoThumbNail(_referenceXFileVid!),
builder: (context,uint8) {
if(uint8.hasData){
final ImagePath = uint8.data;
final CurrentThumbImg = Image.memory(ImagePath!,
height: 267,
width: 150,
fit: BoxFit.cover,);
SaveVidAndThumb(_referenceVidFile!,File.fromRawPath(ImagePath));
return CurrentThumbImg;
}else{
return Center(child: Text('Saumya Made Mistake',textAlign: TextAlign.center,));
}
}) : Text('No Video Taken',textAlign: TextAlign.center,),
),
SizedBox(width: 10,),
Expanded(
child: FlatButton.icon(onPressed: (){
TakePic();
},
icon: Icon(Icons.camera),
label: Text('Take Video'),
textColor: Theme.of(context).primaryColor,),
),
],
);
}
void SaveVidAndThumb(File Video,File Thumbnail) async{
final appDir = await syspath.getApplicationDocumentsDirectory();
final VideoPath = p.basename(Video.path);
final ThumbPath = p.basename(Thumbnail.path);
final Savedvideo = await Video.copy('${appDir.path}/$VideoPath');
final SavedThumb = await Thumbnail.copy('${appDir.path}/$ThumbPath');
widget.onSelected(Savedvideo,SavedThumb);
}
}
ADD PLACES SCREEN :
import 'dart:io';
import 'package:memory_place/models/Video_format.dart';
import 'package:memory_place/providers/place_provider.dart';
import 'package:memory_place/widgets/Location_Input.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:memory_place/widgets/Image_Input.dart';
class AddScreen extends StatelessWidget {
TextEditingController _TitleTextController = TextEditingController();
late File _CurrentVideo;
late File _CurrentThumbnail;
void CallCurrentImage(File CurrentVideo,File CurrentThumbnail){
if(CurrentVideo!=null){
_CurrentVideo = CurrentVideo;
_CurrentThumbnail = CurrentThumbnail;
}
}
#override
Widget build(BuildContext context) {
void SafePlace(){
if(_TitleTextController.text == null || _CurrentThumbnail == null){
throw Text('Either Title or Image is Missing');
}else {
Provider.of<PlaceProvider>(context, listen: false).AddPlace(
_TitleTextController.text,
_CurrentVideo,
_CurrentThumbnail);
Navigator.pop(context);
}
}
return Scaffold(
appBar: AppBar(
title: Text('Add Place Screen'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child:SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(label: Text('Title...')),
controller: _TitleTextController,
),
SizedBox(height: 20,),
ImageInput(onSelected: CallCurrentImage),
SizedBox(height: 20,),
LocationInput(),
],
),
),
) ),
RaisedButton.icon(
onPressed: (){
SafePlace();
},
icon: Icon(Icons.add),
label: Text('Add Place'),
elevation: 0,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: Theme.of(context).accentColor,
)
],
),
);
}
}
When you declare a variable as late , you are actually indicating that it won't be null.
In your code, you're declaring_CurrentThumbnail variable as late but not
instantiating it.
You can change your AddScreen widget to StatefulWidget and instantiate _CurrentThumbnail inside initState function.(or even call CallCurrentImage inside initState)
I'm trying to fetch data when I reach the last item scrolling the list view, but the event isn't triggering.
As soon as I end up with the last item, the scrollController is used for listening to the scrolling activity and should trigger the bloc event to fetch more data but it isn't happening.
Here is the code for that part:
class OrderHistoryPage extends StatelessWidget {
const OrderHistoryPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<OrderHistoryWatcherBloc>()
..add(const OrderHistoryWatcherEvent.fetchOrderHistory()),
child: PageBuilder(),
);
}
}
class PageBuilder extends StatelessWidget {
final scrollController = ScrollController();
PageBuilder({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<OrderHistoryWatcherBloc, OrderHistoryWatcherState>(
builder: (context, state) {
if (state.isProcessing) {
return const Scaffold(
body: CustomLoadingIndicator(),
);
} else {
return state.failureOrSuccess?.fold(
(l) => Scaffold(
body: FailedContainer(
onTap: () =>
context.read<OrderHistoryWatcherBloc>().add(
const OrderHistoryWatcherEvent
.fetchOrderHistory(),
),
),
), (r) {
setupScrollController(context, r.next);
var ordersList = r.results;
if (state.searchKey != null) {
ordersList = ordersList
.where((element) => element.id
.toString()
.toLowerCase()
.contains(state.searchKey!.toLowerCase()))
.toList();
}
return MainScaffold(
requiredAppBar: true,
pageName: pageName,
requiredAppDrawer: true,
body: Column(
children: [
CustomSearchBox(
icon: Icons.search_sharp,
hintText: 'Search Orders by Id',
onChanged: (v) {
context
.read<OrderHistoryWatcherBloc>()
.add(OrderHistoryWatcherEvent.searchKey(v));
},
),
sizedBoxedSmall,
Expanded(
child: RefreshIndicator(
onRefresh: () async {
context.read<OrderHistoryWatcherBloc>().add(
const OrderHistoryWatcherEvent
.fetchOrderHistory());
},
child: ordersList.isNotEmpty
? ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
controller: scrollController,
itemBuilder: (context, index) {
if (index < ordersList.length) {
return OrderListTile(
orderId: ordersList[index].id,
createdDateTime:
ordersList[index].created,
totalPrice: ordersList[index].total,
orderStatus: ordersList[index].status);
} else {
Timer(const Duration(milliseconds: 30), () {
scrollController.jumpTo(scrollController
.position.maxScrollExtent);
});
return const Center(
child: CircularProgressIndicator());
}
},
itemCount: ordersList.length +
(state.isLoading ? 1 : 0),
)
: const Text('Search result not found'),
),
),
],
),
);
}) ??
Container();
}
});
}
void setupScrollController(context, String? url) {
scrollController.addListener(() {
if (scrollController.position.atEdge) {
if (scrollController.position.pixels != 0) {
debugPrint('from order history page : $url');
context
.read<OrderHistoryWatcherBloc>()
.add(OrderHistoryWatcherEvent.fetchOrderHistory(url: url));
}
}
});
}
}
Here is the error log:
══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
The following NoSuchMethodError was thrown while dispatching notifications for ScrollController:
Class 'StatefulElement' has no instance method 'read'.
Receiver: Instance of 'StatefulElement'
Tried calling: read()
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
#1 PageBuilder.setupScrollController. (package:mitho_food_vendor/features/order/presentation/pages/order_history_page.dart:130:16)
#2 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:308:24)
#3 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:308:24)
#4 ScrollPosition.notifyListeners (package:flutter/src/widgets/scroll_position.dart:968:11)
#5 ScrollPosition.forcePixels (package:flutter/src/widgets/scroll_position.dart:380:5)
#6 ScrollPositionWithSingleContext.jumpTo (package:flutter/src/widgets/scroll_position_with_single_context.dart:198:7)
#7 ScrollController.jumpTo (package:flutter/src/widgets/scroll_controller.dart:173:16)
#8 PageBuilder.build.... (package:mitho_food_vendor/features/order/presentation/pages/order_history_page.dart:101:56)
(elided 11 frames from class _RawReceivePortImpl, class _Timer, dart:async, and dart:async-patch)
The ScrollController sending notification was:
ScrollController#0c24c(one client, offset 34.4)
After researching for some days, I found that we have to specify BuildContext rather than make it dynamic. As during runtime this field will check the type, but will not check during compilation time. This was the issue that couldn't generate a read function and trigger the bloc event during compilation time when the setupScrollController function was called.
Before
After
Reference: https://blog.csdn.net/Mr_Tony/article/details/111738933
I try to do login page (do the transaction in some Future function) and show the error message by Snackbar.
Click login
Show loading
Future transaction done (back to original page), and then show the error message
Here is the flow I want to achieve (the last part failed):
I don't know how to show the snackBar correctly using the message from future.
showSnackBar need BuildContext but the context inside the signIn page seems no longer valid anymore after the message come back from Future.
I am now using package flutter_hooks and hooks_riverpod for the state management.
My State
class MyState{
MyState({this.data,this.isLoading});
final bool isLoading;
final String data;
MyState copyWith({data, isLoading}) => MyState(data: data, isLoading: isLoading);
}
State control and provider
Future<String> getData() fetch data and return error message
class MyStateNotifier extends StateNotifier<MyState> {
MyStateNotifier(MyState state) : super(state);
Future<String> getData() async {
state = state.copyWith(isLoading: true);
await Future.delayed(Duration(seconds: 3)); // simulate getting data
state = state.copyWith(isLoading: false, data: 'some data');
return 'error message';
}
}
final myStateProvider = StateNotifierProvider<MyStateNotifier>((ref) {
return MyStateNotifier(MyState(data: null, isLoading: false));
});
My widget
myState.isLoading: show loading page or sign in page
class WidgetA extends HookWidget {
const WidgetA({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final myState = useProvider(myStateProvider.state);
return Center(
child: myState.isLoading ? CircularProgressIndicator() : SignInPage(),
);
}
}
class SignInPage extends HookWidget {
const SignInPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () async {
context.read(myStateProvider).getData().then(
(message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
},
);
},
child: Text('login'),
);
}
}
I use showSnackBar inside then after getData(), but it show the error message:
E/flutter ( 6869): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Looking up a
deactivated widget's ancestor is unsafe.
E/flutter ( 6869): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 6869): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 6869): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3938:9)
E/flutter ( 6869): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3952:6)
E/flutter ( 6869): #2 Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:4044:12)
E/flutter ( 6869): #3 debugCheckHasScaffoldMessenger.<anonymous closure> (package:flutter/src/material/debug.dart:142:17)
E/flutter ( 6869): #4 debugCheckHasScaffoldMessenger (package:flutter/src/material/debug.dart:154:4)
E/flutter ( 6869): #5 ScaffoldMessenger.of (package:flutter/src/material/scaffold.dart:218:12)
E/flutter ( 6869): #6 SignInPage.build.<anonymous closure>.<anonymous closure> (package:flutter_app_test2/main.dart:171:35)
E/flutter ( 6869): #7 _rootRunUnary (dart:async/zone.dart:1198:47)
E/flutter ( 6869): #8 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter ( 6869): #9 _FutureListener.handleValue (dart:async/future_impl.dart:143:18)
E/flutter ( 6869): #10 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45)
E/flutter ( 6869): #11 Future._propagateToListeners (dart:async/future_impl.dart:725:32)
E/flutter ( 6869): #12 Future._completeWithValue (dart:async/future_impl.dart:529:5)
E/flutter ( 6869): #13 _completeOnAsyncReturn (dart:async-patch/async_patch.dart:254:13)
E/flutter ( 6869): #14 MyStateNotifier.getData (package:flutter_app_test2/main.dart)
E/flutter ( 6869): <asynchronous suspension>
You can copy paste run full code below
Reason : Because SignInPage disappear after click login button
Quick fix is use ScaffoldMessenger and provide scaffoldMessengerKey then call scaffoldMessengerKey.currentState.showSnackBar(SnackBar(content: Text(message)));
code snippet
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
...
context.read(myStateProvider).getData().then(
(message) {
scaffoldMessengerKey.currentState
.showSnackBar(SnackBar(content: Text(message)));
},
);
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/all.dart';
class MyState {
MyState({this.data, this.isLoading});
final bool isLoading;
final String data;
MyState copyWith({data, isLoading}) =>
MyState(data: data, isLoading: isLoading);
}
class MyStateNotifier extends StateNotifier<MyState> {
MyStateNotifier(MyState state) : super(state);
Future<String> getData() async {
state = state.copyWith(isLoading: true);
await Future.delayed(Duration(seconds: 3)); // simulate getting data
state = state.copyWith(isLoading: false, data: 'some data');
return 'error message';
}
}
final myStateProvider = StateNotifierProvider<MyStateNotifier>((ref) {
return MyStateNotifier(MyState(data: null, isLoading: false));
});
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
appBar: AppBar(title: const Text('example')),
body: WidgetA(),
),
);
}
}
class WidgetA extends HookWidget {
const WidgetA({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final myState = useProvider(myStateProvider.state);
return Center(
child: myState.isLoading ? CircularProgressIndicator() : SignInPage(),
);
}
}
class SignInPage extends HookWidget {
const SignInPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () async {
context.read(myStateProvider).getData().then(
(message) {
scaffoldMessengerKey.currentState
.showSnackBar(SnackBar(content: Text(message)));
},
);
},
child: Text('login'),
);
}
}
You'll have to await you getData() request and return the message in a variable, then call the snackbar. You're trying to call the snackbar inside the future call. This can't be done on the UI.
final message = await context.read(myStateProvider).getData();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
My custom widget code:
class CustomWidget extends StatefulWidget {
final int index;
final String value;
final bool isSelected;
final VoidCallback onSelect;
const CustomWidget({
Key key,
#required this.index,
#required this.value,
#required this.isSelected,
#required this.onSelect,
}) : assert(index != null),
assert(value != null),
assert(isSelected != null),
assert(onSelect != null),
super(key: key);
#override
_CustomWidgetState createState() => _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onSelect,
child: Container(
margin: EdgeInsets.all(5.0),
child: ListTile(
title: Text(widget.index == 1 ? " ${widget.value} ${widget.index}" : "${widget.value}" ),
),
decoration: widget.isSelected
? BoxDecoration(color: Colors.black38, border: Border.all(color: Colors.black))
: BoxDecoration(),
),
);
}
}
My Application using custom widget
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentSelectedIndex;
List<ListModel> _list = [
ListModel(
text: "Flutter.dev",
index: 0,
),
ListModel(
text: "Inducesmile.com",
index: 1,
),
ListModel(
text: "Google.com",
index: 2,
),
ListModel(
text: "Yahoo.com",
index: 3,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Selected index is $currentSelectedIndex'),
),
body: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, index) {
return CustomWidget(
index: index,
value: _list[index].text,
isSelected: currentSelectedIndex == index,
onSelect: () {
setState(() {
currentSelectedIndex = index;
});
},
);
},
),
);
}
}
ListModel code:
class ListModel {
String text;
int index;
ListModel({this.text, this.index});
}
Widget test code i tried
testWidgets("Find text", (WidgetTester tester) async {
final testableWidget = MyApp();
await tester.pumpWidget(testableWidget);
expect(find.widgetWithText(CustomWidget,"Inducesmile.com 1"), findsOneWidget);
});
An Error message:
Error: The following TestFailure object was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: ?:<zero widgets with type "CustomWidget" which is an ancestor of text "Inducesmile.com 1">
When the exception was thrown, this was the stack:
#4 main.<anonymous closure> (file:///Users/testUser/Documents/my_app/test/widget_test.dart:23:5)
<asynchronous suspension>
#5 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:82:23)
#6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:571:19)
<asynchronous suspension>
#9 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:555:14)
Which: means none were found but one was expected
How can i tap the particular list item and verify it out that item it contains is what the widget has or not ?
I also want to verify list items count?
But it even not find the simple text
I had the same issue. I had my custom widget inside listview and needed to test the widget inside the children of the listview.
At the beginning I called:
debugDumpApp()
to see if the widget exists in the widget tree. (and it did exist)
Later I found that you need to set shrinkWrap to true in ListView. and It worked
Can you try like this:
ListModel(
key: Key("Inducesmile"),
text: "Inducesmile.com",
index: 1,
),
And then
expect(find.byKey(Key("Inducesmile")), findsOneWidget);