How infinite scroll Flutter over REST pagination with provider? - flutter

I created a flutter project with provider package
Previously it had run well using ScopedModel following this sample project.
I want to implement v3 provider with the same logic,
// main.dart
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) => NoteContentModel()),
],
...
home: NoteContentPage(),
),
);
}
// note_content_model.dart
class NoteContentModel extends ChangeNotifier {
final _pageSize = 30;
List<Content> _content = [];
bool _isLoading = false;
int _totalResults;
int _totalPages;
bool _hasMorePages = true;
String _placeName;
bool _isLoadingMore = false;
// .. other setter getter
int getNoteCount() => _content.length;
Future<dynamic> _getData([int page = 1]) async {
var res = await http.get(getUrl(page));
return jsonDecode(res.body);
}
Future getNoteContent([int page = 1]) async {
if (page == 1) {
_isLoading = true;
_content.clear();
} else {
_isLoadingMore = true;
}
notifyListeners();
var responseData = await _getData();
List noteContent = responseData['content'];
noteContent.forEach((content) {
_content.add(Content.fromJson(content));
});
_totalResults = responseData['total_count'];
_totalPages = responseData['page_count'];
if (responseData['page_number'] == totalPages) {
_hasMorePages = false;
}
if (page == 1) {
_isLoading = false;
} else {
_isLoadingMore = false;
}
notifyListeners();
}
}
// note_content_page.dart
class NoteContentPage extends StatefulWidget {
#override
_NoteContentPageState createState() => _NoteContentPageState();
}
class _NoteContentPageState extends State<NoteContentPage> {
int page = 1;
ScrollController controller;
void _scrollListener() {
final NoteContentModel noteModel = Provider.of<NoteContentModel>(context);
if (controller.position.pixels == controller.position.maxScrollExtent) {
if (!noteModel.isLoadingMore && noteModel.hasMorePages) {
page++;
print("Current page: $page");
noteModel.getNoteContent(page);
}
}
}
#override
void initState() {
super.initState();
controller = new ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
final NoteContentModel noteModel = Provider.of<NoteContentModel>(context);
// Call initiate first page
noteModel.getNoteContent(page);
return Scaffold(
appBar: AppBar(title: Text("Test")),
body: CustomScrollView(
controller: controller,
slivers: <Widget>[
noteModel.isLoading
? SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == noteModel.getNoteCount()) {
if (noteModel.hasMorePages) {
print("here1");
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Center(child: CircularProgressIndicator()),
);
}
return Container();
} else {
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(noteModel.content[index].title),
// child: Text("test"),
),
],
);
}
},
childCount: noteModel.getNoteCount(),
),
)
],
),
);
}
}
I always got this message loop markNeedsBuild() called during build end trace error
I/flutter ( 2525): ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
I/flutter ( 2525): The following assertion was thrown while dispatching notifications for NoteContentModel:
I/flutter ( 2525): setState() or markNeedsBuild() called during build.
I/flutter ( 2525): This ListenableProvider<NoteContentModel> widget cannot be marked as needing to build because the
...
I/flutter ( 2525): #0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3670:11)
I/flutter ( 2525): #1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3685:6)
I/flutter ( 2525): #2 State.setState (package:flutter/src/widgets/framework.dart:1161:14)
I/flutter ( 2525): #3 __BuilderListenableDelegate&BuilderStateDelegate&_ListenableDelegateMixin.startListening.<anonymous closure> (package:provider/src/listenable_provider.dart:186:36)
I/flutter ( 2525): #4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:206:21)
I/flutter ( 2525): #5 NoteContentModel.getNoteContent (package:paging_provider/providers/note_content_model.dart:42:5)
I/flutter ( 2525): <asynchronous suspension>
I/flutter ( 2525): #6 _NoteContentPageState.build (package:paging_provider/pages/note_content_page.dart:44:15)
I/flutter ( 2525): #7 StatefulElement.build (package:flutter/src/widgets/framework.dart:4012:27)
I/flutter ( 2525): #8 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3924:15)
I/flutter ( 2525): #9 Element.rebuild (package:flutter/src/widgets/framework.dart:3721:5)
I/flutter ( 2525): #10 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3907:5)
...
// This trace always loop on app running
I/flutter ( 2525): Another exception was thrown: setState() or markNeedsBuild() called during build....
so that's all, any response will be appreciated.

You must wrap your functions in addPostFrameCallback():
So the call is submitted after the build function finish it work and draw widgets over screen
//Change
noteModel.getNoteContent(page);
//To
WidgetsBinding.instance.addPostFrameCallback((_) => noteModel.getNoteContent(page);
);
Hope this answer being valuable for someone.

Related

Failed assertion: line 1205 pos 12: '<optimized out>': is not true

I am new to flutter and facing an issue in Syncfusion Calendar widget. Any help will be appreciated. Here is the code inside my build widget:
return Scaffold(
resizeToAvoidBottomInset: false, // set it to false
body: SafeArea(
child: SfCalendar(
view: CalendarView.month,
dataSource:getData(),
monthViewSettings: MonthViewSettings(showAgenda: true,),
),
),
);
'dart:ui/painting.dart': Failed assertion: line 1205 pos 12: '': is not true.
Here is the exception line:4 > child: SfCalendar()
Here is the stacktrace:
**> The relevant error-causing widget was: SfCalendar
file:///Users/kashif/Desktop/AppRack/clique/lib/bottomDashboard/Calendar.dart:47:16
When the exception was thrown, this was the stack:
#2 Paint.color= (dart:ui/painting.dart:1205:12)
#3 _AppointmentRenderObject._drawMonthAppointmentIndicator (package:syncfusion_flutter_calendar/src/calendar/appointment_layout/appointment_layout.dart:2042:15)
#4 _AppointmentRenderObject._drawMonthAppointment (package:syncfusion_flutter_calendar/src/calendar/appointment_layout/appointment_layout.dart:1698:9)
#5 _AppointmentRenderObject._drawCustomAppointmentView (package:syncfusion_flutter_calendar/src/calendar/appointment_layout/appointment_layout.dart:1662:11)
#6 _AppointmentRenderObject.paint (package:syncfusion_flutter_calendar/src/calendar/appointment_layout/appointment_layout.dart:1614:7)
#7 RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:2396:7)
#8 PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:139:11)
#9 PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:100:5)
#10 PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:975:29)
#11 RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:464:19)
#12 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:879:13)
#13 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:328:5)
#14 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1143:15)
#15 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1080:9)
#16 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:996:5)
#20 _invoke (dart:ui/hooks.dart:163:10)
#21 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:259:5)
#22 _drawFrame (dart:ui/hooks.dart:126:31) (elided 5 frames from class _AssertionError and dart:async) The following RenderObject was
being processed when the exception was fired:
_AppointmentRenderObject#b4ead ... needs compositing ... parentData: (can use size) ... constraints: BoxConstraints(w=411.4,
h=452.8) ... layer: OffsetLayer#2d0bc ... engine layer:
OffsetEngineLayer#fc8e9 ... offset: Offset(0.0, 0.0) ... size:
Size(411.4, 452.8) RenderObject: _AppointmentRenderObject#b4ead
needs compositing parentData: (can use size) constraints:
BoxConstraints(w=411.4, h=452.8) layer: OffsetLayer#2d0bc
engine layer: OffsetEngineLayer#fc8e9
offset: Offset(0.0, 0.0) size: Size(411.4, 452.8)
====================================================================================================**
Based on the provided information we have checked, and we could not reproduce the mentioned issue from our end. We have prepared a simple sample for the same, based on the given stack, we suspect that appointment color set as null in the sample. Please find the code snippet for the same.
Code snippet:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
void main() => runApp(AgendaView());
class AgendaView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Agenda(),
);
}
}
class Agenda extends StatefulWidget {
#override
State<StatefulWidget> createState() => AgendaExample();
}
class AgendaExample extends State<Agenda> {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: SfCalendar(
view: CalendarView.month,
dataSource: MeetingDateSource(_getDataSource()),
monthViewSettings: MonthViewSettings(showAgenda: true),
),
),
);
}
}
List<Meeting> _getDataSource() {
List<Meeting> meets = <Meeting>[];
meets.add(Meeting('Meeting', DateTime.now(),
DateTime.now().add(Duration(minutes: 30)), Colors.pink, true));
return meets;
}
class MeetingDateSource extends CalendarDataSource {
MeetingDateSource(List<Meeting> source) {
appointments = source;
}
#override
String getSubject(int index) {
return appointments![index].eventname;
}
#override
DateTime getStartTime(int index) {
return appointments![index].from;
}
#override
DateTime getEndTime(int index) {
return appointments![index].to;
}
#override
Color getColor(int index) {
return appointments![index].bgclr;
}
#override
bool isAllDay(int index) {
return appointments![index].bval;
}
}
class Meeting {
Meeting(this.eventname, this.from, this.to, this.bgclr, this.bval);
String eventname;
DateTime from;
DateTime to;
Color bgclr;
bool bval;
}
If the shared information does not meet your requirement, could you please modify the shared sample to replicate the issue or if possible, share the calendar code snippets used with replication procedure, so that we could analyze further and provide you a possible solution at the earliest.

Flutter Admob's widget get error when setState is call

I have a problem with Admob's widgets.
I am developing a new feature for a flutter app that contains an Admob banner widget.
But when I setState the value of another widget, the Admob Widget gets an error.
I am using :google_mobile_ads: ^0.11.0+1
The banner is build like so:
#override
void initState() {
setState(() {
_adBanner = createBannerAd();
});
super.initState();
}
#override
void dispose() {
_adBanner.dispose();
super.dispose();
}
And the widget is display like this:
Container(
margin: EdgeInsets.only(bottom: myPercent(2, screenHeight)),
child: FutureBuilder(
future: _adBanner.load(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
margin: EdgeInsets.only(bottom: 3),
width: myPercent(95, screenWidth),
height: myPercent(6, screenHeight),
alignment: Alignment.center,
child: AdWidget(
ad: _adBanner,
),
);
}
return Container();
}),
The log error catch :
flutter: click
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building AdWidget(dirty, state: _AdWidgetState#a1afb):
flutter: This AdWidget is already in the Widget tree
flutter: If you placed this AdWidget in a list, make sure you create a new instance in the builder function
flutter: with a unique ad object.
flutter: Make sure you are not using the same ad object in more than one AdWidget.
flutter:
flutter: The relevant error-causing widget was:
flutter: AdWidget file:///Users/sofian/Work/Personal/Mobile/WhatUDo/what_u_do/lib/views/idea.dart:295:34
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 _AdWidgetState.build (package:google_mobile_ads/src/ad_containers.dart:372:7)
flutter: #1 StatefulElement.build (package:flutter/src/widgets/framework.dart:4825:27)
flutter: #2 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4708:15)
flutter: #3 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4880:11)
flutter: #4 BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #5 Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #6 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4687:5)
flutter: #7 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4871:11)
flutter: #8 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4682:5)
flutter: ... Normal element mounting (10 frames)
flutter: #18 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3660:14)
flutter: #19 Element.updateChild (package:flutter/src/widgets/framework.dart:3422:20)
flutter: #20 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4733:16)
flutter: #21 BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #22 Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #23 StatelessElement.update (package:flutter/src/widgets/framework.dart:4789:5)
flutter: #24 Element.updateChild (package:flutter/src/widgets/framework.dart:3412:15)
flutter: #25 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4733:16)
flutter: #26 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4880:11)
flutter: #27 BuildOwner._runWithCurrentBuildTarget (package:flutter/src/widgets/framework.dart:2708:15)
flutter: #28 Element.rebuild (package:flutter/src/widgets/framework.dart:4407:12)
flutter: #29 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2813:33)
flutter: #30 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:899:21)
flutter: #31 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:320:5)
flutter: #32 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1119:15)
flutter: #33 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1057:9)
flutter: #34 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:973:5)
flutter: #38 _invoke (dart:ui/hooks.dart:157:10)
flutter: #39 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:253:5)
flutter: #40 _drawFrame (dart:ui/hooks.dart:120:31)
flutter: (elided 3 frames from dart:async)
As from question and error log, seems like you are trying to fresh screen/widget using setState but not the actual ad based container widget - still you are getting this error.
If I understood correctly.. then issue is about ad widget is trying to rebuild on setState call with older ad object but it expect new ad object everytime new build. So, avoid those kind of widget build if that's not required.
create a seprate ad based container widget like AppXyzAdWidget and move the ad's parent container code and other ad related code inside the new widget and use the newly created ad based widget on your screen.
This way you can decouple your screen without ad related things then after setState will not reload your ad and their widget just only refresh your content.
Calling setState() causes the widget to rebuild, so your FutureBuilder() widget fires the future task again. This is why you're seeing this error.
You need to remove FutureBuilder() and move your future task to initState():
// ...
// Some code
#override
void initState() {
setState(() {
_adBanner = createBannerAd();
});
_adBanner.load().whenComplete(() {
if (this.mounted) {
setState(() {
_showAdBanner = true;
});
}
});
super.initState();
}
#override
void dispose() {
_adBanner.dispose();
super.dispose();
}
// ...
// Some code
You can also have boolean to make the AdWidget() appear when the future task is complete:
bool _showAdBanner = false;
// ...
// Some code
_showAdBanner
? Container(
margin: EdgeInsets.only(bottom: 3),
width: myPercent(95, screenWidth),
height: myPercent(6, screenHeight),
alignment: Alignment.center,
child: AdWidget(
ad: _adBanner,
),
)
: Container()
// ...
// Some code
According to the google ads package example
googleads-mobile-flutter
You have to use didChangeDependencies
class BannarAdWidget extends StatefulWidget {
const BannarAdWidget({Key? key}) : super(key: key);
#override
State<BannarAdWidget> createState() => _BannarAdWidgetState();
}
class _BannarAdWidgetState extends State<BannarAdWidget> {
BannerAd? _bannerAd;
bool _bannerAdIsLoaded = false;
#override
void didChangeDependencies() {
// Create the ad objects and load ads.
_bannerAd = BannerAd(
size: AdSize.banner,
request: const AdRequest(),
adUnitId: AdmobHelper.bannerAdUnitId,
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
print('$BannerAd loaded.');
setState(() => _bannerAdIsLoaded = true);
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
print('$BannerAd failedToLoad: $error');
ad.dispose();
},
onAdOpened: (Ad ad) => print('$BannerAd onAdOpened.'),
onAdClosed: (Ad ad) => print('$BannerAd onAdClosed.'),
),
)..load();
super.didChangeDependencies();
}
#override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final BannerAd? bannerAd = _bannerAd;
if (_bannerAdIsLoaded && bannerAd != null) {
return SizedBox(
height: bannerAd.size.height.toDouble(),
width: bannerAd.size.width.toDouble(),
child: AdWidget(ad: bannerAd),
);
} else {
return const SizedBox.shrink();
}
}
class AdmobHelper {
static String get bannerAdUnitId {
if (Platform.isAndroid) {
return "ca-app-pub-3940256099942544/6300978111";
} else if (Platform.isIOS) {
return "ca-app-pub-3940256099942544/2934735716";
} else {
throw UnsupportedError('Unsupported platform');
}
}
}
Can you remove initState code and initialize _adBanner in your build function like this ?
#override
Widget build(BuildContext context) {
_adBanner = createBannerAd();
return Container(
margin: EdgeInsets.only(bottom: myPercent(2, screenHeight)),
child: FutureBuilder(
future: _adBanner.load(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
margin: EdgeInsets.only(bottom: 3),
width: myPercent(95, screenWidth),
height: myPercent(6, screenHeight),
alignment: Alignment.center,
child: AdWidget(
ad: _adBanner,
),
);
}
return Container();
});
}

Show snackbar message after widget rebuild

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)));

How to read file txt from storage (Flutter)?

I want to read a config file from a folder on my phone. But I manage to read a part of file and I want to be able to read the entire file. Any Ideas about how to do it the right way?
Here is my code :
import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:ext_storage/ext_storage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// String _platformVersion = 'Unknown';
String _configFile = 'No Data';
static Future<String> get _getPath async {
final localPath = await ExtStorage.getExternalStoragePublicDirectory(
ExtStorage.DIRECTORY_DOWNLOADS);
return localPath;
}
static Future<File> get _localFile async {
final path = await _getPath;
return File('$path/config_test.txt');
}
static Future<String> readConfig() async {
try {
final file = await _localFile;
// Read the file.
String contents = await file.readAsString();
return contents;
} catch (e) {
// If encountering an error, return 0.
return "error";
}
}
#override
void initState() {
super.initState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (await FlutterOpenvpn.init(
localizedDescription: "ExampleVPN",
providerBundleIdentifier:
"com.topfreelancerdeveloper.flutterOpenvpnExample.RunnerExtension",
)) {
await FlutterOpenvpn.lunchVpn(
_configFile,
(isProfileLoaded) => print('isProfileLoaded : $isProfileLoaded'),
(vpnActivated) => print('vpnActivated : $vpnActivated'),
//expireAt: DateTime.now().add(Duration(seconds: 30)),
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('OpenVPN connect Test'),
),
body: Center(
child: Column(
children: <Widget>[
IconButton(
iconSize: 48.0,
splashColor: Colors.greenAccent,
onPressed: _startBtnVpn,
icon: Icon(Icons.play_arrow)),
IconButton(
iconSize: 48.0,
splashColor: Colors.greenAccent,
icon: Icon(Icons.stop),
onPressed: _stopBtnVPN,
),
IconButton(
iconSize: 48.0,
splashColor: Colors.greenAccent,
icon: Icon(Icons.cloud),
onPressed: () {
readConfig().then((value) {
setState(() {
_configFile = value;
print(_configFile);
});
});
}),
],
),
),
),
);
}
void _startBtnVpn() {
setState(() {
initPlatformState();
});
}
void _stopBtnVPN() {
FlutterOpenvpn.stopVPN();
}
}
DEBUG CONSOLE:
I/flutter (17341): "dev tun\n" +
I/flutter (17341): "proto udp\n" +
I/flutter (17341): "remote public-vpn-255.opengw.net 1195\n" +
I/flutter (17341): ";http-proxy-retry\n" +
I/flutter (17341): ";http-proxy [proxy server] [proxy port]\n" +
I/flutter (17341): "cipher AES-128-CBC\n" +
I/flutter (17341): "auth SHA1\n" +
I/flutter (17341): "resolv-retry infinite\n" +
I/flutter (17341): "nobind\n" +
I/flutter (17341): "persist-key\n" +
I/flutter (17341): "persist-tun\n" +
I/flutter (17341): "client\n" +
I/flutter (17341): "verb 3\n" +
I/flutter (17341): "<ca>\n" +
I/flutter (17341): "-----BEGIN CERTIFICATE-----\n" +
I/flutter (17341): "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\n" +
I/flutter (17341): "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n" +
I/flutter (17341): "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n" +
I/flutter (17341): "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\n" +
I/flutter (17341): "MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\n" +
I/flutter (17341): "BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\n" +
I/flutter (17341): "aGUgVVNFUlR
The problem is that the print function has a limitation for printing outputs to the console, and it doesn't print long strings completely. Try setting a break point using your IDE or editor and check out the value read from the file.

Error when I try to show a SnackBar in a builder

This is my main.dart:
class MyApp extends StatelessWidget {
var login = new Login("xxxxxxx", "xxxxxxxxx");
final scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder(
future: login.main(),
builder: (context, snapshot) {
if (snapshot.hasData) {
_showSnackBar(snapshot.data);
return new Container();
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
),
),
),
);
}
_showSnackBar(var text) {
scaffoldKey.currentState
.showSnackBar(new SnackBar(content: new Text(text)));
}
}
I'd like to show a SnackBar in FutureBuilder, but when I run, something wrong occurs:
I/flutter ( 8918): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════
I/flutter ( 8918): The following assertion was thrown building
FutureBuilder<dynamic>(dirty, state:
I/flutter ( 8918): _FutureBuilderState<dynamic>#9d4af):
I/flutter ( 8918): setState() or markNeedsBuild() called during build.
I/flutter ( 8918): This Scaffold widget cannot be marked as needing to
build because the framework is already in the
I/flutter ( 8918): process of building widgets. A widget can be marked
as needing to be built during the build phase
I/flutter ( 8918): only if one of its ancestors is currently building.
This exception is allowed because the framework
I/flutter ( 8918): builds parent widgets before children, which means
a dirty descendant will always be built.
I/flutter ( 8918): Otherwise, the framework might not visit this
widget during this build phase.
I/flutter ( 8918): The widget on which setState() or markNeedsBuild()
was called was:
I/flutter ( 8918): Scaffold-[LabeledGlobalKey<ScaffoldState>#e4a3f]
(state: ScaffoldState#cf908(tickers: tracking 2
I/flutter ( 8918): tickers))
I/flutter ( 8918): The widget which was currently being built when the
offending call was made was:
I/flutter ( 8918): FutureBuilder<dynamic>(dirty, state: _
FutureBuilderState<dynamic>#9d4af)
I/flutter ( 8918):
I/flutter ( 8918): When the exception was thrown, this was the stack:
I/flutter ( 8918): #0 Element.markNeedsBuild.<anonymous closure>
(package:flutter/src/widgets/framework.dart:3472:11)
I/flutter ( 8918): #1 Element.markNeedsBuild
(package:flutter/src/widgets/framework.dart:3498:6)
I/flutter ( 8918): #2 State.setState
(package:flutter/src/widgets/framework.dart:1141:14)
I/flutter ( 8918): #3 ScaffoldState.showSnackBar
(package:flutter/src/material/scaffold.dart:1110:5)
I/flutter ( 8918): #4 MyApp._showSnackBar
I/flutter ( 8918): #5 MyApp.build.<anonymous closure>
I/flutter ( 8918): #6 _FutureBuilderState.build
(package:flutter/src/widgets/async.dart)
I/flutter ( 8918): #7 StatefulElement.build
(package:flutter/src/widgets/framework.dart:3766:27)
I/flutter ( 8918): #8 ComponentElement.performRebuild
(package:flutter/src/widgets/framework.dart:3678:15)
I/flutter ( 8918): #9 Element.rebuild
(package:flutter/src/widgets/framework.dart:3531:5)
I/flutter ( 8918): #10 BuildOwner.buildScope
(package:flutter/src/widgets/framework.dart:2273:33)
I/flutter ( 8918): #11
My purpose is that it shows a loading spinner before it gets the data, and once it got the data, it shows a SnackBar. So what should I do to fix the problem? Login.main is a function that GET data from the database.
FutureBuilder or StreamBuilder should only used to build widgets, not to execute logic like navigation. Use instead initState like
#override
void initState() {
super.initState();
_checkLogin();
}
Future<void> _checkLogin() async {
try {
var data = await login.main();
setState(() {
_waitForLogin = false;
});
_showSnackBar(data);
} catch(e) {
setState(() {
_waitForLogin = false;
_loginErrorMessage = '$e';
});
}
}
bool _isWaitForLogin = true;
String _loginErrorMessage;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: _isWaitForLogin ? CircularProgressIndicator() :
(_loginErrorMessage != null ? Text(_loginErrorMessage : Container())
),
),
);
}
The solution that #thought7878 recommended suggests wrapping the breaking logic in WidgetsBinding.instance.addPostFrameCallback((_) { }. For example:
builder: (context, AsyncSnapshot<Object> snapshot) {
if (snapshot.hasError) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(
// SnackBar
);
});
return Container();
}
// Rest ...
}
Could you try like this: modify _showSnackBar and pass the context
_showSnackBar(var text, BuildContext context) {
ScaffoldKey.of(context)
.showSnackBar(new SnackBar(content: new Text(text)));
}