Flutter exception Builder keeps being called - flutter

I am using a flutter Image.file() to show an Image in my app. I am also using the errorBuilder to handle any crashes and show a message to the user.
I encounter a problem when i do these steps.
Load a good image that works
Load a corrupt image into the same Image.File() widget
Load the original good image back into the same Image.File() widget
It seems every file change after the corrupt photo is passed in (step 2) will result in the error builder being shown and not the new good image.
If i don't pass in the corrupt photo in step 2, the photo changes like it should. Is this a bug with the flutter Image() or should I be doing something after it goes into the errorBuilder.
Here is my current setup.
Image.file(
file,
fit: BoxFit.cover,
errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
print("Failed to initialise the file");
print(stackTrace);
// Once an error occurs it always goes in here
return Text("an error occurred");
},
);
The actual error I receive on all file changes when/after the corrupt file is passed in is
Could not instantiate image codec.
UPDATE
I have wrote a dartpad that shows the problem i am experiencing.
https://dartpad.dev/98c2dacb481c088dfd2e5bee490f45ed
If you click
Good Image
Good Image 2
Good Image
Good Image 2
The images cycle correctly... which works.
if you then click "Corrupt Image" which will attempt to load a corrupt jpeg file the error builder will fire.
If you then click "Good Image" or "Good Image 2" they no longer build and the Image is stuck loading the error builder everytime... How can I get it to then load one of the good images again?
Please let me know if I haven't been clear and I will add more information :)
Thanks a lot

You can copy paste run full code below
This error can be fixed with add key: UniqueKey(),
I have test it with DardPad works fine
code snippet
Image.network(
_selectedImageURL,
key: UniqueKey(),
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _goodImageOne =
"https://upload.wikimedia.org/wikipedia/commons/6/69/June_odd-eyed-cat_cropped.jpg";
String _corruptImage =
"https://srv-file16.gofile.io/download/hwTzLI/cat_corrupt.jpg";
String _goodImageTwo =
"https://upload.wikimedia.org/wikipedia/commons/c/c7/Tabby_cat_with_blue_eyes-3336579.jpg";
String _selectedImageURL;
#override
void initState() {
super.initState();
_selectedImageURL = _goodImageOne;
}
void _changeFile(String newUrl) {
setState(() {
_selectedImageURL = newUrl;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(
_selectedImageURL,
key: UniqueKey(),
height: 300,
width: 200,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes
: null,
));
},
errorBuilder: (BuildContext context, Object exception,
StackTrace stackTrace) {
return Text("Cannot display url");
},
),
Expanded(child: Container()),
RaisedButton(
onPressed: () => _changeFile(_goodImageOne),
child: Text("Good Image"),
),
RaisedButton(
onPressed: () => _changeFile(_corruptImage),
child: Text("Corrupt Image"),
),
RaisedButton(
onPressed: () => _changeFile(_goodImageTwo),
child: Text("Good Image 2"),
),
],
),
),
);
}
}

Related

Is there a neat way to pop a page and schedule a callback to be called after navigation is complete?

I want to call Navigator.of(context).pop() one or several times and then run a callback after navigation has completed, but I have struggled to find a neat solution. I've put together an example app to illustrate the problem I'm having:
Screens A, B, and C all access a nullable value on the Model Provider
ScreenA can set value to a non-null value
ScreenB requires value to be non-null to build
ScreenC can set value to null and pop you back to ScreenA
When you press the button on ScreenC to go back to ScreenA, it navigates successfully (the app doesn't crash) but you throw an Error because it tries to build ScreenB after the first pop.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => Model(),
child: MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ScreenA(),
),
);
}
}
class Model extends ChangeNotifier {
int? value = 0;
Future<void> updateValue(int? newValue) async {
await Future.delayed(const Duration(milliseconds: 30));
value = newValue;
notifyListeners();
}
}
class ScreenA extends StatelessWidget {
const ScreenA({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
Text('ScreenA - value: ${context.watch<Model>().value}'),
ElevatedButton(
child: const Text('Set value'),
onPressed: () => context.read<Model>().updateValue(Random().nextInt(100)),
),
ElevatedButton(
child: const Text('Go to B'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => ScreenB(
nonNullValue: context.watch<Model>().value ?? (throw Error()),
),
),
),
),
],
),
);
}
}
class ScreenB extends StatelessWidget {
const ScreenB({Key? key, required this.nonNullValue}) : super(key: key);
final int nonNullValue;
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
Text('ScreenB - value: $nonNullValue'),
ElevatedButton(
child: const Text('Set value'),
onPressed: () => context.read<Model>().updateValue(Random().nextInt(100)),
),
ElevatedButton(
child: const Text('Go to C'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const ScreenC(),
),
),
),
],
),
);
}
}
class ScreenC extends StatelessWidget {
const ScreenC({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: centredScreenContent(
[
const Spacer(),
const Text('ScreenC'),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
context.read<Model>().updateValue(null);
},
child: const Text('Reset app')),
const Spacer(),
],
),
);
}
}
Widget centredScreenContent(List<Widget> widgets) => Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: widgets,
),
);
I've found two solutions, but neither feels neat:
Make ScreenB take a nullable value in its constructor, and have its build return something like value == null ? Container() : ActualContents(nonNullValue: value!). I don't like this though. If we know that in BAU use, ScreenB cannot be built while value == null, then we'd like to log an error if that happens in production so we can investigate the problem. We can't do this if our navigation back from ScreenC also hits this state though.
Add a sufficiently long delay to the callback so that it runs after the navigation is completed, e.g. in the example app, if you change Model.updateValue to have a 300ms delay, then it doesn't error. This also feels like an unpleasant solution, if the delay is too long we risk the app behaving sluggishly, if it's too short then we don't solve the problem at all.
I would make ScreenB(int? nullableParam) and handle the widget builder with additional assert nullableParam == null just to log the error.
But what i think the real solution you are looking for is context.read<Model>().value instead of watch - i can't think of a scenario where you want to page parameter depend on any listenable state
solution
ElevatedButton(
child: const Text('Go to B'),
onPressed: () async => await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => ScreenB(
nonNullValue: context.read<Model>().value ?? (throw Error()),
This way the page after first build will not be rebuild with null when popped.
#Edit
I see 2 problems:
passing Listenable value to Page parameter
purposely setting value to null where other part of application purposely is not handling it
The first one can be solved with the solution above
The second you have to either assure the passed value will not be null on Navigator.pop() - the solution above does that. Or handle the null value in the ScreenB widget (as you suggested with conditional build)

_TypeError (type 'Null' is not a subtype of type 'Widget')

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Basit ToDo Uygulaması",
home: Iskele(),
);
}
}
class Iskele extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ToDo Uygulaması"),
),
body: AnaEkran(),
);
}
}
class AnaEkran extends StatefulWidget {
#override
_AnaEkranState createState() => _AnaEkranState();
}
class _AnaEkranState extends State<AnaEkran> {
TextEditingController t1 = TextEditingController();
List alisverisListesi = [];
elemanEkle() {
setState(() {
alisverisListesi.add(t1.text);
t1.clear();
});
}
elemanCikar() {
setState(() {
alisverisListesi.remove(t1.text);
t1.clear();
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Flexible(
child: ListView.builder(
itemCount: alisverisListesi.length,
itemBuilder: (context, siraNumarasi) => ListTile(
subtitle: Text("Marketten Alınacaklar"),
title: Text(alisverisListesi[siraNumarasi]),
),
),
),
TextField(
controller: t1,
),
RaisedButton(
onPressed: elemanEkle,
child: Text("Ekle"),
),
RaisedButton(
onPressed: elemanCikar,
child: Text("Çıkar"),
),
],
),
);
}
RaisedButton({required Function() onPressed, required Text child}) {}
}
**
main.dart
**
!!
I don't understand why I'm getting this error, can you help me? I am
trying to make a simple app in Flutter but I have this error in front
of me.
I don't understand why I'm getting this error, can you help me? I am
trying to make a simple app in Flutter but I have this error in front
of me.
Remove line 82: RaisedButton({required Function() onPressed, required Text child}) {}. I don't actually understand what this line is supposed to do, can you explain that?
I do know why this caused an issue though. This is interpreted as a function without a return type. This causes the return type to be dynamic, essentially disabling dart's typesystem. That is why the compiler doesn't complain. Then this function returns null and throws this error.
Another thing, RaisedButton is depricated. Please don't use it. Use ElevatedButton instead.

I cannot see my pictures in flutter swiper

I am new in flutter development. I cannot find my mistake. I can't see my images on my app. when I use them without slider it works what is wrong in my code can someone help me?
import 'package:feedme_start/widgets/Navigation_Drawer_Widget.dart';
import 'package:flutter/material.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:flutter_swiper/flutter_swiper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
final imageList = [
"https://cdn.yeniakit.com.tr/images/news/625/pratik-degisik-yemek-tarifleri-en-sevilen-tarifler-h1581081558-3ff37b.jpg"
"https://cdn.yemek.com/mncrop/940/625/uploads/2021/04/patlicanli-pilav-yemekcom.jpg"
];
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.red,
title: Center(child: Text("FEED ME")),
actions: <Widget>[
IconButton(onPressed: () {}, icon: Icon(Icons.call))
],
),
drawer: NavigationDrawerWidget(),
backgroundColor: Colors.white,
body:
Container(
constraints: BoxConstraints.expand(height: 200),
child: imageSlider(context),
),
/*Swiper(itemCount: imageList.length,
itemBuilder: (context, index) {
return Image.network(imageList[index],/*errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace), {return const("resim yüklenemedi")},*/
fit: BoxFit.cover,);
},)*/
),
);
}
}
Swiper imageSlider(context){
return new Swiper(
autoplay: true,
itemBuilder: (BuildContext context, int index) {
return new Image.network("https://cdn.yeniakit.com.tr/images/news/625/pratik-degisik-yemek-tarifleri-en-sevilen-tarifler-h1581081558-3ff37b.jpg",fit: BoxFit.fitHeight,);
},
itemCount: 10,
viewportFraction: 0.8,
scale: 0.9,
);
}
also here is the screenshots;
when I run the program it tries to upload the image. then it sends me the this screen and shows the 'rethrow' line error:
after I continue debugging my screen looks like in the second picture:
You're simply forgetting to add , in your list.
A newline string after a string is considered as a string. For example, the following variable:
var text = "one two three"
"four five six";
is the same as:
var text = "one two three" + "four five six";
So, instead of:
final imageList = [
"https://cdn.yeniakit.com.tr/images/news/625/pratik-degisik-yemek-tarifleri-en-sevilen-tarifler-h1581081558-3ff37b.jpg"
"https://cdn.yemek.com/mncrop/940/625/uploads/2021/04/patlicanli-pilav-yemekcom.jpg"
];
change to:
final imageList = [
"https://cdn.yeniakit.com.tr/images/news/625/pratik-degisik-yemek-tarifleri-en-sevilen-tarifler-h1581081558-3ff37b.jpg" ,
"https://cdn.yemek.com/mncrop/940/625/uploads/2021/04/patlicanli-pilav-yemekcom.jpg"
];

Why is Flutter dialog not rebuilding on change notifier?

Well the issue is kinda simple, but it needs to be done on a specific way. First I have a Class extending "ChangeNotifier" this class will perform some async tasks, so while it is doing so there's a variable that indicates if the class is currently bussy or not, so far it works flawlessly.
Using Riverpod as state managment I instanciate said class and provide it along my widget tree, but there's one Widget that needs to display a dialog and inside this dialog it can execute async tasks from the Class that I've been passing around. It all works except for the fact that I would like to display a CircularProgressIndicator inside this dialog, and it doesn't seems to be reacting propperly to the state changes.
Here's a sample code to recreate the scenario:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final dataProvider = ChangeNotifierProvider<Data>((_) => Data());
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'huh?',
theme: ThemeData(primarySwatch: Colors.blue),
home: FirstPage(),
);
}
}
class FirstPage extends HookWidget {
#override
Widget build(BuildContext context) {
final data = useProvider(dataProvider);
print('DATA STATE [source: FirstPage, data: ${data.loading}]');
return Scaffold(
body: Center(
child: Container(
width: 200,
height: 50,
child: ElevatedButton(
child: Text('show dialog'),
onPressed: () => showDialog(
context: context,
builder: (_) => Alert(data: data),
),
),
),
),
);
}
}
class Alert extends StatelessWidget {
const Alert({required this.data});
final Data data;
Widget build(BuildContext context) {
print('DATA STATE [source: Alert, data: ${data.loading}]');
return AlertDialog(
content: Container(
width: 500,
height: 500,
padding: EdgeInsets.symmetric(horizontal: 100, vertical: 200),
child: ElevatedButton(
child: data.loading ? CircularProgressIndicator(color: Colors.white) : Text('click here'),
onPressed: () async => await data.randomTask(),
),
),
);
}
}
class Data extends ChangeNotifier {
Data({
this.loading = false,
});
bool loading;
Future<void> randomTask() async {
print('Actually waiting 3 seconds..');
_update(loading: true);
await Future.delayed(Duration(seconds: 3));
print('Waiting done.');
_update(loading: false);
}
void _update({bool? loading}) {
this.loading = loading ?? this.loading;
notifyListeners();
}
}
Notice the prints I've placed, because of them if you run the app you'll see outputs on the console like:
DATA STATE [source: FirstPage, data: false]
DATA STATE [source: Alert, data: false]
Actually waiting 3 seconds..
DATA STATE [source: FirstPage, data: true]
Waiting done.
DATA STATE [source: FirstPage, data: false]
Which means that the state is actually changing, and everything is working fine, except for the dialog that seems to be static.
I already tried adding a "loading" bool as part of the "Alert" widget, and letting it manage its own state, and it works, but the code is not as clean as I would like to, because the Class "Data" is supposed to manage this kind of stuff.
Is there anything that can be done?
Thankyou in advance!
Adding StatefulBulider do the trick
class Alert extends StatelessWidget {
const Alert({required this.data});
final Data data;
Widget build(BuildContext context) {
print('DATA STATE [source: Alert, data: ${data.loading}]');
return AlertDialog(
content: StatefulBuilder(builder: (context, setState) {
return Container(
width: 500,
height: 500,
padding: EdgeInsets.symmetric(horizontal: 100, vertical: 200),
child: ElevatedButton(
child: data.loading
? CircularProgressIndicator(color: Colors.white)
: Text('click here'),
onPressed: () async => await data.randomTask(),
),
);
}),
);
}
}

How to control gif animation in Flutter?

I'm trying to restart an animated gif on Flutter. The gif image loads from network without a problem and animates after loading. I need to restart the animation on tapping a button.
Tried so far:
- setState
- change Key to some other unique key and setState to rebuild.
Solution as #chemamolins 's suggestion:
int _robotReloadCount=0;
....
GestureDetector(
onTap: () {
onTapRobot();
},
child: Center(
child: Container(
margin: EdgeInsets.only(top: 55.0, bottom: 5.0),
height: 150.0,
width: 150.0,
child:
FadeInImage(
key: this._robotImageKey,
placeholder: AssetImage('assets/common/robot_placeholder.png'),
image: NetworkImage(snapshot.data['robot_image_path'] +"robot_level" +snapshot.data['robot_level'].toString() +".gif"+"?"+this._robotReloadCount.toString()))),
),
),
....
onTapRobot() async{
setState(() {
this._robotReloadCount++;
});
}
I have done a lot of tests and it is not easy. The image is cached by the 'ImageProvider' and whatever you change or no matter the times you invoke build() the image is loaded from what is available in the cache.
So, apparently, you only have two options.
Either you rebuild with a new url, for instance by appending #whatever to the image url.
Or you remove the image from the cache as shown in the code below.
In either case you need to fetch again the image from the network.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String url = "https://media.giphy.com/media/hIfDZ869b7EHu/giphy.gif";
void _evictImage() {
final NetworkImage provider = NetworkImage(url);
provider.evict().then<void>((bool success) {
if (success) debugPrint('removed image!');
});
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: Image.network(url),
),
floatingActionButton: new FloatingActionButton(
onPressed: _evictImage,
child: new Icon(Icons.remove),
),
);
}
}