NoSuchMethodError when taking Screenshot - flutter

I am trying to take a Screanshot of a Stak with a list of iteams in it. It displays normaly and works, but when i try to take screenshot of the Widget I resive:
NoSuchMethodError (NoSuchMethodError: The getter 'stateWidget' was called on null.
Receiver: null
Tried calling: stateWidget)
(I use a Inhereted widget)
Her is the Widget I am trying to take a Screenshot of
class BlinkSkjerm extends StatelessWidget {
#override
Widget build(BuildContext context) {
final provider = InheritedDataProvider.of(context);
final data = provider.historikken[provider.index];
return SizedBox(
height: 400,
child: Stack(
children: data.inMoveableItemsList,
));
}
}
and her is the onPress funtion:
onPressed: () async {
final controler = ScreenshotController();
final bytes = await controler.captureFromWidget(BlinkSkjerm());
setState(() {
this.bytes = bytes;
});
}

you used InheritedDataProvider in wrong way. you did not provide data that needed in BlinkSkjerm.
you want to take screen shot from widget that not in the tree, but that widget need data that should provide before build it which you did not provide it.
this approach work this way:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InheritedDataProvider(
child: BlinkSkjerm(),
data:'some string',
)),
);
this way you can use
final provider = InheritedDataProvider.of(context);
and make sure it is not null.
for your situation I recommended to do something like this:
onPressed: () async {
final controler = ScreenshotController();
final bytes = await controler.captureFromWidget(InheritedDataProvider(
child: BlinkSkjerm(),
data:'some string',
));
setState(() {
this.bytes = bytes;
});
}
for more information see this page

Related

Flutter awesome notifications how to fix StateError (Bad state: Stream has already been listened to.)

I am getting this error when I have signed out from my flutter app and trying to log in again:
StateError (Bad state: Stream has already been listened to.)
The code that gives me this error is on my first page:
#override
void initState() {
AwesomeNotifications().actionStream.listen((notification) async {
if (notification.channelKey == 'scheduled_channel') {
var payload = notification.payload['payload'];
var value = await FirebaseFirestore.instance
.collection(widget.user.uid)
.doc(payload)
.get();
navigatorKey.currentState.push(PageRouteBuilder(
pageBuilder: (_, __, ___) => DetailPage(
user: widget.user,
i: 0,
docname: payload,
color: value.data()['color'].toString(),
createdDate: int.parse((value.data()['date'].toString())),
documentId: value.data()['documentId'].toString(),)));
}
});
super.initState();
}
And on another page that contains the sign out code.
await FirebaseAuth.instance.signOut();
if (!mounted) return;
Navigator.pushNamedAndRemoveUntil(context,
"/login", (Route<dynamic> route) => false);
What can I do to solve this? Is it possible to stop listen to actionstream when I log out? Or should I do it in another way?
Streams over all are single use, they replace the callback hell that that ui is, at first a single use streams can seem useless but that may be for a lack of foresight. Over all (at lest for me) flutter provides all the necessary widgets to not get messy with streams, you can find them in the Implementers section of ChangeNotifier and all of those implement others like TextEditingController.
With that, an ideal (again, at least for me) is to treat widgets as clusters where streams just tie them in a use case, for example, the widget StreamBuilder is designed to build on demand so it only needs something that pumps changes to make a "live object" like in a clock, a periodic function adds a new value to the stream and the widget just needs to listen and update.
To fix your problem you can make .actionStream fit the case you are using it or change a bit how are you using it (having a monkey patch is not good but you decide if it is worth it).
This example is not exactly a "this is what is wrong, fix it", it is more to showcase a use of how pushNamedAndRemoveUntil and StreamSubscription can get implemented. I also used a InheritedWidget just because is so useful in this cases. One thing you should check a bit more is that the variable count does not stop incrementing when route_a is not in focus, the stream is independent and it will be alive as long as the widget is, which in your case, rebuilding the listening widget is the error.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(App());
const String route_a = '/route_a';
const String route_b = '/route_b';
const String route_c = '/route_c';
class App extends StatelessWidget {
Stream<int> gen_nums() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield 1;
}
}
#override
Widget build(BuildContext ctx) {
return ReachableData(
child: MaterialApp(
initialRoute: route_a,
routes: <String, WidgetBuilder>{
route_a: (_) => Something(stream: gen_nums()),
route_b: (_) => FillerRoute(),
route_c: (_) => SetMount(),
},
),
);
}
}
class ReachableData extends InheritedWidget {
final data = ReachableDataState();
ReachableData({super.key, required super.child});
static ReachableData of(BuildContext ctx) {
final result = ctx.dependOnInheritedWidgetOfExactType<ReachableData>();
assert(result != null, 'Context error');
return result!;
}
#override
bool updateShouldNotify(ReachableData old) => false;
}
class ReachableDataState {
String? mount;
}
// route a
class Something extends StatefulWidget {
// If this widget needs to be disposed then use the other
// constructor and this call in the routes:
// Something(subscription: gen_nums().listen(null)),
// final StreamSubscription<int> subscription;
// Something({required this.subscription, super.key});
final Stream<int> stream;
Something({required this.stream, super.key});
#override
State<Something> createState() => _Something();
}
class _Something extends State<Something> {
int count = 0;
void increment_by(int i) => setState(
() => count += i,
);
#override
void initState() {
super.initState();
widget.stream.listen(increment_by);
// To avoid any funny errors you should set the subscription
// on pause or the callback to null on dispose
// widget.subscription.onData(increment_by);
}
#override
Widget build(BuildContext ctx) {
var mount = ReachableData.of(ctx).data.mount ?? 'No mount';
return Scaffold(
body: InkWell(
child: Text('[$count] Push Other / $mount'),
onTap: () {
ReachableData.of(ctx).data.mount = null;
Navigator.of(ctx).pushNamed(route_b);
},
),
);
}
}
// route b
class FillerRoute extends StatelessWidget {
const FillerRoute({super.key});
#override
Widget build(BuildContext ctx) {
return Scaffold(
body: InkWell(
child: Text('Go next'),
// Option 1: go to the next route
// onTap: () => Navigator.of(ctx).pushNamed(route_c),
// Option 2: go to the next route and extend the pop
onTap: () => Navigator.of(ctx)
.pushNamedAndRemoveUntil(route_c, ModalRoute.withName(route_a)),
),
);
}
}
// route c
class SetMount extends StatelessWidget {
const SetMount({super.key});
#override
Widget build(BuildContext ctx) {
return Scaffold(
body: InkWell(
child: Text('Set Mount'),
onTap: () {
ReachableData.of(ctx).data.mount = 'Mounted';
// Option 1: pop untill reaches the correct route
// Navigator.of(ctx).popUntil(ModalRoute.withName(route_a));
// Option 2: a regular pop
Navigator.of(ctx).pop();
},
),
);
}
}

ChangeNotifier inaccessible in grandchildren widget of where it was provided

I am trying to use flutter provider in order to carry my state down a widget sub-tree/route, and while it works for the direct child of the widget that provided the change notifier class, it does not for the next one in line.
As far as I understand, the change notifier class should be passed down. To be more specific, I am trying to access it through context.read() in a function being called in its initState function.
Am I doing something wrong?
The code below illustrates my code.
Where it class notifier is provided:
onTap: () {
// Select body area
context.read<Patient>().selectBodyArea(areas[index]);
// Open complaint list
FlowRepresentation flow = context.read<Patient>().getFlow();
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider.value(
value: flow,
child: const ChiefComplaintList()
)
)
);
}
Navigation to the problem widget in ChiefComplaintList:
onTap: () {
// Select complaint
context.read<FlowRepresentation>().selectComplaint(ccs[index]);
// Show factors
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AttenuationFactors())
);
}
Where I'm having trouble accessing the change notifier class:
void getData() async {
_nrFactors = await context.read<FlowRepresentation>().getAttenuationFactors();
setState(() {}); // rebuild widget with data
}
#override
void initState() {
super.initState();
print("Initiated Attenuation Factors Lists State");
getData();
}

shared preferences does not save radio button checkmark in Flutter

I implemented the shared preferences package in my Flutter app, with a list widget as radio button, that only save the language preference and not the checkmark.
So when i close the Language screen and come back, the language checkmark goes the the default one even if the language, saved in shared preferences is French or Italian.
This is my Language screen:
class LanguagesScreen extends StatefulWidget {
const LanguagesScreen({Key? key}) : super(key: key);
#override
State<LanguagesScreen> createState() => _LanguagesScreenState();
}
class Item {
final String prefix;
final String? helper;
const Item({required this.prefix, this.helper});
}
var items = [
Item(prefix: 'English', helper: 'English',), //value: 'English'
Item(prefix: 'Français', helper: 'French'),
Item(prefix: 'Italiano', helper: 'Italian'),
];
class _LanguagesScreenState extends State<LanguagesScreen> {
var _selectedIndex = 0;
final _userPref = UserPreferences();
var _selecLangIndex;
int index = 0;
final List<String> entries = <String>['English', 'French', 'Italian'];*/
//init shared preferences
#override
void initState() {
super .initState();
_populateField();
}
void _populateField() async {
var prefSettings = await _userPref.getPrefSettings();
setState((){
_selecLangIndex = prefSettings.language;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(...
),
body: CupertinoPageScaffold(
child: Container(
child: SingleChildScrollView(
child: CupertinoFormSection.insetGrouped(
children: [
...List.generate(items.length, (index) => GestureDetector(
onTap: () async {
setState(() => _selectedIndex = index);
if (index == 0){
await context.setLocale(Locale('en','US'));
_selecIndex = Language.English;
}
else if (index == 1){
await context.setLocale(Locale('fr','FR'));
_selecIndex = Language.French;
}
child: buildCupertinoFormRow(
items[index].prefix,
items[index].helper,
selected: _selectedIndex == index,
)
)),
TextButton(onPressed:
_saveSettings,
child: Text('save',
)
buildCupertinoFormRow(String prefix, String? helper, {bool selected = false,}) {
return CupertinoFormRow(
prefix: Text(prefix),
helper: helper != null
? Text(helper, style: Theme.of(context).textTheme.bodySmall,)
:null, child: selected ? const Icon(CupertinoIcons.check_mark,
color: Colors.blue, size: 20,) :Container(),
);
}
void _saveSettings() {
final newSettings = PrefSettings(language:_selecIndex);
_userPref.saveSettings(newSettings);
Navigator.pop(context);
}
}
this is the UserPreference:
class UserPreferences {
Future saveSettings(PrefSettings prefSettings) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setInt('language' , prefSettings.language.index );
}
Future<PrefSettings> getPrefSettings() async {
final preferences = await SharedPreferences.getInstance();
final language = Language.values[preferences.getInt('language') ?? 0 ];
return PrefSettings(language: language);
}
}
enum Language { English, French, Italian}
class PrefSettings{
final Language language;
PrefSettings (
{required this.language});
}
I'm betting that the issue is in initState. You are calling _populateField, but it doesn't complete before building because it's an async method, and you can't await for it: so the widget gets build, loading the default position for the checkmark, and only after that _populateField completes...but then it's too late to show the saved data correctly.
In my experience, if I have not already instantiated a SharedPreferences object somewhere else in the code, I use this to load it:
class _LanguagesScreenState extends State<LanguagesScreen> {
[...]
#override
Widget build(BuildContext context) {
return FutureBuilder(
//you can put any async method here, just be
//sure that you use the type it returns later when using 'snapshot.data as T'
future: await SharedPreferences.getInstance(),
builder: (context, snapshot) {
//error handling
if (!snapshot.hasData || snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
var prefs= snapshot.data as SharedPreferences;
//now you have all the preferences available without need to await for them
return Scaffold((
[...]
);
EDIT
I started writing another comment, but there are so many options here that there wasn't enough space.
First, the code I posted should go in your _LanguagesScreenState build method. The FutureBuilder I suggested should wrap anything that depends on the Future you must wait for to complete. I put it up at the root, above Scaffold, but you can move it down the widgets' tree as you need, just remember that everything that needs to read the preferences has to be inside the FutureBuilder.
Second, regarding SharedPreferences.getInstance(), there are two ways: the first is declaring it as a global variable, and loading it even in the main method where everything starts. By doing this you'll be able to reference it from anywhere in your code, just be careful to save the changes everytime is needed. The second is to load it everytime you need, but you'll end up using a FutureBuilder a lot. I don't know if any of these two options is better than the other: the first might have problems if somehow the SharedPreferences object gets lost, while the second requires quite more code to work.

Flutter get data that was returned to Drawer from Navigator.pop()

I have the main/homepage widget of my app, let's call it home.dart.
Inside this widget, I have defined the drawer key in my Scaffold widget. The code for the Drawer object is in a separate file, navdrawer.dart.
home.dart
import 'navdrawer.dart';
. . .
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavDrawer(),
...
Now inside NavDrawer, I construct my Drawer widget which has a settings button, which links to the settings.dart screen.
Which I do like this:
navdrawer.dart
. . .
InkWell(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
print(result);
},
child: ListTile(
leading: Icon(
Icons.settings,
color: AppTextColor,
),
title: Text('Settings'))),
So now, when the user presses the back button on the settings page, the Navigator.pop() call will return the data I need to the result variable in navdrawer.dart.
But my problem is ... how do I get this data to my home.dart screen/state?
I'll suggest you to use provider, scoped_model or other state management techniques for this. The easiest (but also the worthless) solution would be to use a global variable.
However, there's a middle ground. For simplicity I'm using dynamic for the type of result, you'd better know what Settings return, so use that type instead.
Your home.dart file
class _HomePageState extends State<HomePage> {
dynamic _result; // Create a variable.
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: NavDrawer(
onResult: (result) {
_result = result; // <-- This is your result.
}
),
);
}
}
Add following in your navdrawer.dart:
class NavDrawer extends StatelessWidget {
// Add these two lines.
final ValueChanged onResult;
NavDrawer({this.onResult});
// Other code ...
}
Modify your onTap method inside navdrawer.dart file:
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
onResult(result); // <-- Add this line.
}
Please set parameters into the pop method.
Like
Navigator.pop(context,true)
Define 'Static' global variable in homepage screen/widget
Then call the variable from anywhere :
1- homepage :
Static String getData;
2- when returned to Drawer :
homepage.getData=value;
Navigator.pop();

Flutter setState not updating child element

I have an InkWell which uses onTap to perform some actions. When the button is tapped, I like an indicator to be shown (in case the action is long-running). However, the setState in the InkWell does not trigger its children to be re-rendered. The code is as follows:
class PrimaryButtonState extends State<PrimaryButton> {
bool _apiCall;
Widget getWidget() {
if(_apiCall) {
return new CircularProgressIndicator();
} else {
return Text(
widget.label,
);
}
}
#override
Widget build(BuildContext context) {
final List<Color> colors = //omitted
return InkWell(
child: Container(
decoration: // omitted
child: getWidget(), // not updated when _apiCall changes !!!!!
),
onTap: () {
setState(() {
_apiCall = true;
});
widget.onTab(context);
setState(() {
_apiCall = false;
});
}
);
}
}
How can I solve this that getWidget returns the correct widget dependent on _apiCall?
EDIT:
The widget.onTap contains the following:
void performLogin(BuildContext context) {
final String userName = _userName.text.trim();
final String password = _password.text.trim();
UserService.get().loginUser(userName, password).then((val) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => MainLayout()));
}).catchError((e) {
// omitted
});
}
it is passed with the widget:
class PrimaryButton extends StatefulWidget {
final bool isPrimary;
final String label;
final Function(BuildContext context) onTab;
PrimaryButton(this.label, this.isPrimary, this.onTab);
#override
State<StatefulWidget> createState() => PrimaryButtonState();
}
My main concern is, that the given onTap method should not know it is "bound" to a UI widget and therefore should not setState. Also, as this is a general button implementation I like it to be interchangeable (therefore, onTap is not hardcoded)
It looks like your problem is because you are calling setState() twice in your onTap() function. Since onTap() is not an async function it will set _apiCall = true in the first setState, then immediately run widget.onTab(context) and then immediately perform the second setState() to set _apiCall = false so you never see the loading widget.
To fix this you will need to make your onTab function an async function and await for a value in your onTap function for your InkWell:
onTap: () async {
setState(() {
_apiCall = true;
});
await widget.onTab(context);
setState(() {
_apiCall = false;
});
}
This will also let you use the results of your onTab function to show errors or other functionality if needed.
If you are unsure how to use async functions and futures here is a good guide on it that goes over this exact kind of use case.