I have made a simple test case app where you click a widget using GestureDetector which triggers an update using setState to the tapCount variable.
The app is working in the emulator with the text updating correctly as shown above, but as soon as I try a Flutter widget test, the widget test fails as the text does not update correctly in the test environment.
Reproducible example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp();
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int tapCount = 0;
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
MyImage(
onTap: () {
setState(() {
tapCount += 1;
});
},
imagePath: 'assets/my-image.jpg',
),
Text(tapCount.toString())
],
),
),
),
);
}
}
class MyImage extends StatelessWidget {
final Function() onTap;
final String imagePath;
const MyImage({
Key key,
#required this.onTap,
#required this.imagePath,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
this.onTap();
},
child: Image.asset(
imagePath,
height: 100.0,
),
);
}
}
In the pubspec, I downloaded a random image and verified the image successfully displays in the emulator.
assets:
- assets/my-image.jpg
My test is the same as the sample with the addition of await tester.pumpAndSettle(); and tapping the image:
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the image and trigger a frame.
await tester.tap(find.byType(MyImage));
await tester.pump();
await tester.pumpAndSettle();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing); // this test fails
expect(find.text('1'), findsOneWidget); // this test fails
});
}
When I run the test I get this error
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
Expected: no matching nodes in the widget tree
Actual: ?:<exactly one widget with text "0" (ignoring offstage widgets): Text("0")>
Which: means one was found but none were expected
When the exception was thrown, this was the stack:
#4 main.<anonymous closure> (file:///Projects/untitled/test/widget_test.dart:27: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:566:19)
<asynchronous suspension>
#9 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:550:14)
#10 AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:893:24)
#16 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:890:15)
#17 testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:81:22)
#18 Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)
<asynchronous suspension>
#19 Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:249:15)
<asynchronous suspension>
#24 Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:246:5)
#25 Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:166:33)
#30 Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:165:13)
<asynchronous suspension>
#31 Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:399:25)
<asynchronous suspension>
#45 _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
#46 _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
#47 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)
(elided 28 frames from class _FakeAsync, package dart:async, and package stack_trace)
This was caught by the test expectation on the following line:
file:///Projects/untitled/test/widget_test.dart line 27
The test description was:
Counter increments smoke test
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: Counter increments smoke test
If I try the same test, but with the Image inside MyImage replaced with another widget (e.g. another Text widget) inside main.dart, the test passes:
class MyImage extends StatelessWidget {
final Function() onTap;
final String imagePath;
const MyImage({
Key key,
#required this.onTap,
#required this.imagePath,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
this.onTap();
},
child: Text( // replaced Image with Text and test passes!
imagePath,
),
);
}
}
This makes me think the issue is due to using the Image, but I can't figure out why.
Code is also uploaded on GitHub if you want to try the test.
Here is my take on why it's not working with an image. The flutter tests run in a FakeAsync zone and when you need to run real async code like loading an asset through an assetBundle the asset is not getting loaded and the image widget's size stays as zero and because of this the hit testing fails. If you set height and width of the image before hand the test passes.
Related
I build a function that is used to store the token in shared preference and then fetch data from the server when I run my app for the very first time an error appears
The following NoSuchMethodError was thrown building AdminPage(dirty,
state: AdminPageState#87bcd): flutter: The method '[]' was called on
null. flutter: Receiver: null flutter: Tried calling:
and then the app works fine
P.S. my code is that
Future<Map<String, dynamic>> getCards(String userid) async {
BuildContext context;
String jWTtoken = '';
try {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs = await SharedPreferences.getInstance();
jWTtoken = prefs.getString('token');
tokenfoo();
} catch (e) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) => AuthPage()),
);
}
final Map<String, dynamic> authData = {
'Userid': '261',
// 'Email':_formData['Email'],
// 'Password':_formData['Password'],
};
final http.Response response = await http.post(
'hurl',
body: json.encode(authData),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + jWTtoken
});
final Map<String, dynamic> responseData = json.decode(response.body);
if (responseData["StatusCode"] == 200) {
null;
} else if (responseData["StatusCode"] == 401) {
print(responseData);
Logout();
} else {
print(responseData);
Logout();
null;
}
return responseData;
}
anything worng with it?
and in the debug mode the error appears in this line
final SharedPreferences prefs = await SharedPreferences.getInstance();
the error :
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════ flutter:
The following NoSuchMethodError was thrown building AdminPage(dirty,
state: AdminPageState#7db9a): flutter: The method '[]' was called on
null. flutter: Receiver: null flutter: Tried calling: flutter:
flutter: When the exception was thrown, this was the stack: flutter:
0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5) flutter: #1
AdminPageState.build (package:idb/pages/adminpage.dart:63:39) flutter:
2 StatefulElement.build (package:flutter/src/widgets/framework.dart:3809:27) flutter: #3
ComponentElement.performRebuild
(package:flutter/src/widgets/framework.dart:3721:15) flutter: #4
Element.rebuild (package:flutter/src/widgets/framework.dart:3547:5)
flutter: #5 ComponentElement._firstBuild
(package:flutter/src/widgets/framework.dart:3701:5) flutter: #6
StatefulElement._firstBuild
(package:flutter/src/widgets/framework.dart:3848:11) flutter: #7
ComponentElement.mount
(package:flutter/src/widgets/framework.dart:3696:5) flutter: #8
Element.inflateWidget
(package:flutter/src/widgets/framework.dart:2950:14) flutter: #9
Element.updateChild
(package:flutter/src/widgets/framework.dart:2753:12) flutter: #10
ComponentElement.performRebuild
(package:flutter/src/widgets/framework.dart:3732:16) flutter: #11
Element.rebuild (package:flutter/src/widgets/framework.dart:3547:5)
flutter: #12 BuildOwner.buildScope
(package:flutter/src/widgets/framework.dart:2286:33) flutter: #13
_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame
(package:flutter/src/widgets/binding.dart:676:20) flutter: #14
_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback
(package:flutter/src/rendering/binding.dart:219:5) flutter: #15
_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback
(package:flutter/src/scheduler/binding.dart:990:15) flutter: #16
_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame
(package:flutter/src/scheduler/binding.dart:930:9) flutter: #17
_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame
(package:flutter/src/scheduler/binding.dart:842:5) flutter: #18
_invoke (dart:ui/hooks.dart:154:13) flutter: #19 _drawFrame (dart:ui/hooks.dart:143:3)
Possibly because jwToken is empty at initialization. Try giving jwToken a string value such as "test".
Create a global class to use globally:
final SharedPreferences prefs = await SharedPreferences.getInstance();
static Future init() async {
pref = await SharedPreferences.getInstance();
}
I am trying to perform a widget test, specifically navigation test. I am using bloc architecture, setting a stream on the bloc triggers a series of events inside the bloc, gets session info from the server call (which returns a future of session info object), on successful server call a login stream is set and the widget has a stream subscription to this stream and navigates to the next screen.
I am using mockito to mock the server call and stubbing the server call to return a future of success response. The problem is the when I am calling pumpAndSettle() it is getting timed out as it is not waiting for the future to complete and return the success response.
I apologize if I am not making it very clear, but here is the sample code:
login_bloc.dart
class LoginBloc {
LoginRepository _loginRepository;
final String searchKeyword = "special-keyword";
final _urlString = PublishSubject<String>();
final _isLoggedIn = BehaviorSubject<bool>(seedValue: false);
final _errorMessage = PublishSubject<String>();
Observable<bool> get isLoggedIn => _isLoggedIn.stream;
Observable<String> get isErrorState => _errorMessage.stream;
LoginBloc({LoginRepository loginRepository})
: _loginRepository = loginRepository ?? LoginRepository() {
// Listen on the _urlString stream to call the function which checks for the special keyword and if a match is found make a server call
_urlString.stream.listen((String url) {
_authorizationFullService(url);
});
}
// Search for special keyword and if a match is found call the server call function
void _authorizationFullService(String url) {
if (url.contains(searchKeyword)) {
int index = url.indexOf(searchKeyword);
String result = url.substring(index + searchKeyword.length);
result = result.trim();
String decodedUrl = Uri.decodeFull(result);
if (decodedUrl != null && decodedUrl.length > 0) {
_fullServiceServerCall(decodedUrl);
} else {
_isLoggedIn.sink.add(false);
}
}
}
// Call server call function from repository which returns a future of the Authorization object
void _fullServiceServerCall(String decodedUrl) {
_loginRepository
.getSession(decodedUrl)
.then(_handleSuccessAuthorization)
.catchError(_handleErrorState);
}
// Handle success response and set the login stream
void _handleSuccessAuthorization(Authorization authorization) {
if (authorization != null && authorization.idnumber != 0) {
_isLoggedIn.sink.add(true);
} else {
_isLoggedIn.sink.add(false);
}
}
// Handle error response and set the error stream
void _handleErrorState(dynamic error) {
_isLoggedIn.sink.add(false);
_errorMessage.sink.add(error.toString());
}
void dispose() {
_urlString.close();
_isLoggedIn.close();
_errorMessage.close();
}
}
widget_test.dart
group('Full Login Navigation test', () {
LoginRepository mockLoginRepository;
LoginBloc loginBloc;
NotificationBloc notificationBloc;
NavigatorObserver mockNavigatorObserver;
Authorization _auth;
String testUrl;
setUp(() {
mockLoginRepository = MockLoginRepository();
_auth = Authorization((auth) => auth
..param1 = "foo"
..param2 = "bar"
..param3 = "foobar"
..param4 = "barfoo");
loginBloc = LoginBloc(loginRepository: mockLoginRepository);
mockNavigatorObserver = MockNavigatorObserver();
testUrl = "http://test.test.com";
});
Future<Null> _buildFullLoginPage(LoginBloc loginBloc,
NotificationBloc notificationBloc, WidgetTester tester) async {
when(mockLoginRepository.getSession(testUrl))
.thenAnswer((_) => Future.value(_auth));
await tester.pumpWidget(LoginBlocProvider(
child: NotificationBlocProvider(
child: MaterialApp(
home: LoginFullService(),
onGenerateRoute: NavigationRoutes.routes,
navigatorObservers: [mockNavigatorObserver],
),
notificationBloc: notificationBloc,
),
loginBloc: loginBloc,
));
//TODO: Remove casting to dynamic after dart sdk bug fix: https://github.com/dart-lang/mockito/issues/163
verify(mockNavigatorObserver.didPush(any, any) as dynamic);
loginBloc.getAuthorization(
"http://testing.testing.com?search-keyword=http%3A%2F%2Ftest.test.com");
}
testWidgets('Navigate to landing page on correct login url',
(WidgetTester tester) async {
await _buildFullLoginPage(loginBloc, notificationBloc, tester);
await tester.pumpAndSettle();
expect(find.byKey(Key('webview_scaffold')), findsNothing);
//TODO: Remove casting to dynamic after dart sdk bug fix: https://github.com/dart-lang/mockito/issues/163
verify(mockNavigatorObserver.didPush(any, any) as dynamic);
});
});
On running the widget test the tester.pumpAndSettle() inside testWidgets times out before the future is completed. This is the error log:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown running a test:
pumpAndSettle timed out
When the exception was thrown, this was the stack:
#0 WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.dart:299:11)
<asynchronous suspension>
#3 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:69:41)
#4 WidgetTester.pumpAndSettle (package:flutter_test/src/widget_tester.dart:295:27)
#5 main.<anonymous closure>.<anonymous closure> (file:///Users/ssiddh/Documents/projects/mobile-flutter/test/ui/pages/login/login_full_test.dart:114:20)
<asynchronous suspension>
#6 testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:72:23)
#7 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:555:19)
<asynchronous suspension>
#10 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:539:14)
#11 AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:883:24)
#17 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:880:15)
#18 testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:71:22)
#19 Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test/src/backend/declarer.dart:168:27)
<asynchronous suspension>
#20 Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test/src/backend/invoker.dart:249:15)
<asynchronous suspension>
#25 Invoker.waitForOutstandingCallbacks (package:test/src/backend/invoker.dart:246:5)
#26 Declarer.test.<anonymous closure>.<anonymous closure> (package:test/src/backend/declarer.dart:166:33)
#31 Declarer.test.<anonymous closure> (package:test/src/backend/declarer.dart:165:13)
<asynchronous suspension>
#32 Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test/src/backend/invoker.dart:403:25)
<asynchronous suspension>
#46 _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
#47 _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
#48 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:169:12)
(elided 30 frames from class _FakeAsync, package dart:async, and package stack_trace)
I would really appreciate any kind of help or feedback.
Try wrapping your test with
testWidgets('Navigate to landing page on correct login url',
(WidgetTester tester) async {
await tester.runAsync(() async {
// test code here
});
});
After update flutter I have this error bellow, only when I erase app and release a new app. If I just hot reload I havn't this error and map is launch correctly.
I'm certain it's was after update flutter. I saved each blocs validations, and now this issue is repeatable with all previous backup, and I certain that I validate this fonction after remove and reinstall app
in libobject_patch.dart
#patch
dynamic noSuchMethod(Invocation invocation) {
// TODO(regis): Remove temp constructor identifier 'withInvocation'.
throw new NoSuchMethodError.withInvocation(this, invocation);
}
in the console
E/flutter (28250): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (28250): NoSuchMethodError: The method 'substring' was called on null.
E/flutter (28250): Receiver: null
E/flutter (28250): Tried calling: substring(1, 10)
E/flutter (28250): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:46:5)
E/flutter (28250): #1 _ContentState.initState.<anonymous closure> (file:///C:/Users/utilisateur/Desktop/nirbiapp/lib/Content.dart:145:40)
E/flutter (28250): #2 _RootZone.runUnaryGuarded (dart:async/zone.dart:1316:10)
E/flutter (28250): #3 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:330:11)
E/flutter (28250): #4 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:257:7)
E/flutter (28250): #5 _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:132:11)
E/flutter (28250): #6 _MapStream._handleData (dart:async/stream_pipe.dart:232:10)
E/flutter (28250): #7 _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:164:13)
E/flutter (28250): #8 _RootZone.runUnaryGuarded (dart:async/zone.dart:1316:10)
E/flutter (28250): #9 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:330:11)
E/flutter (28250): #10 _DelayedData.perform (dart:async/stream_impl.dart:578:14)
E/flutter (28250): #11 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:694:11)
E/flutter (28250): #12 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:654:7)
E/flutter (28250): #13 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter (28250): #14 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
flutter doctor
C:\flutter\bin\flutter.bat --no-color doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel beta, v0.4.4, on Microsoft Windows [version
10.0.16299.431], locale fr-FR)
[√] Android toolchain - develop for Android devices (Android SDK 27.0.3)
[√] Android Studio (version 3.0)
[√] VS Code, 64-bit edition (version 1.21.1)
[√] Connected devices (1 available)
• No issues found!
Process finished with exit code 0
I have this error when I place showMap here:
_loadgeoValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
lat = (prefs.getDouble('lat'));
long = (prefs.getDouble('long'));
});
showMap();
}
If I launch showMap(); after Onpressed Button I have no issues.
but if I do that, I don't know how to update map with lat and long variable.
Currently I launch the map when I receive sms with coordinate, for this ,
1/ I send sms with Onpressed button
2/ I read the reply with : SmsReceiver().onSmsReceived.listen((SmsMessage msg)
3/ I extract coordinate (lat long) with regex
4/ I save values with shared preference
5/ After saved, I launch mapview with the lat and long coordinate
I have replace if (msg.address == "+33$nirbinumber1") by my own number to test and to be certain of the achievement of the comparaison. Tried calling: substring(1, 10) is remove with the other lines but I have always libobject_patch.dart who is display
Here is the code with substring(1, 10)
String nirbinumber ;
loadnirbinumber() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
nirbinumber = (prefs.getString('number1'));
});
}
#override
void initState() {
super.initState();
loadnirbinumber(); //function to read programed number
new SmsReceiver().onSmsReceived.listen((SmsMessage msg) { // function to listen entry sms
var nirbinumber1= nirbinumber.substring(1, 10); //function to extract first number 0X XX XX XX XX
if (msg.address == "+33$nirbinumber1") // Action after compare input sms with programed number +33X XX XX XX XX
{
setState(() {
_lastMessage = msg;
});
RegExp regExp = new RegExp( //Here is the regex function to isolate first word of sms
r"^([\w\-]+)",
);
var match = regExp.firstMatch(_lastMessage.body);
group1 = match.group(1);
if ( group1 =="maps"){
MapView.setApiKey('AIzaSyCNYMslm35FPWYFGkJVMk7aAOd174TUCGQ');
_savegeoValue(); // this function use shared preference to save, extract, and load map
}
}
}
);
}
I replace by regex, it remove the substring error but no the noSuchMethode
new SmsReceiver().onSmsReceived.listen((SmsMessage msg) {
RegExp regExp = new RegExp(
r"^0([0-9]{9})",
);
var match = regExp.firstMatch(nirbinumber);
group1 = match.group(1);
if (msg.address == "+33$group1") {
setState(() {
_lastMessage = msg;
});
My use-case here is to show a snackbar if there's an error, but I can't catch the SocketException since I'm not calling load: flutter is.
Update (example stacktrace):
I/flutter (11702): ══╡ EXCEPTION CAUGHT BY SERVICES ╞══════════════════════════════════════════════════════════════════
I/flutter (11702): The following SocketException was thrown resolving a single-frame image stream:
I/flutter (11702): Connection failed (OS Error: Network is unreachable, errno = 101), address = (( snip )), port
I/flutter (11702): = 443
I/flutter (11702): When the exception was thrown, this was the stack:
I/flutter (11702): #0 IOClient.send (package:http/src/io_client.dart:30:23)
I/flutter (11702): <asynchronous suspension>
I/flutter (11702): #1 BaseClient._sendUnstreamed (package:http/src/base_client.dart:171:38)
I/flutter (11702): <asynchronous suspension>
I/flutter (11702): #2 BaseClient.get (package:http/src/base_client.dart:34:5)
I/flutter (11702): #3 NetworkImage._loadAsync (package:flutter/src/services/image_provider.dart:431:54)
I/flutter (11702): <asynchronous suspension>
I/flutter (11702): #4 NetworkImage.load (package:flutter/src/services/image_provider.dart:417:7)
I/flutter (11702): #5 ImageProvider.resolve.<anonymous closure>.<anonymous closure> (package:flutter/src/services/image_provider.dart:253:61)
I'm relatively certain it is not possible using NetworkImage and this is because as I understand exceptions can only be caught if you are awaiting the result of what is throwing an exception.
Fortunately with a little F12 action it seems the code is pretty simple. So my solution was to copy NetworkImage's _loadAsync functionality to my own class with a few small tweeks. (Like rturning a Uint8List instead of a codec)
utils/network_image_loader.dart
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:typed_data';
class NetworkImageLoader {
String url;
Map<String, String> headers;
NetworkImageLoader(this.url, {this.headers});
static final http.Client _httpClient = createHttpClient();
Future<Uint8List> load() async {
final Uri resolved = Uri.base.resolve(this.url);
final http.Response response = await _httpClient.get(resolved, headers: headers);
if (response == null || response.statusCode != 200)
throw new Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved');
final Uint8List bytes = response.bodyBytes;
if (bytes.lengthInBytes == 0)
throw new Exception('NetworkImage is an empty file: $resolved');
return bytes;
}
}
A simple way to use this would be (I used it differently since I was loading a loop of images and displaying a default, so I haven't tested this way, but it should work, or be close):
Widget img = new Text("Loading");
...
//Some function, ex constructor, init state, or a callback after you've done an http call
loadImage('http://imawesome.com');
...
#override
build(BuildContext context) {
return img;
}
Future<Uint8List> loadImage(String url) async {
try {
var netImg = new NetworkImageLoader(url);
var res = await netImg.load();
setState(() {
img = new Image(image: new MemoryImage(data),
height: 100.0,
width: 100.0,
fit: BoxFit.contain,
);
});
}
on Exception {
setState(() {
img = new Text("Load Failed");
}
}
}
So my class is nice if you want to do any of the following:
Display something in place of the image until it loads
Handle exceptions loading images (or event just ignore them)
Listen to "events" about image loading (ex. do something when it's loaded)
Edit:
You may want to look into fade in images as well, which allow specifying a placeholder and may be sufficient for many cases: https://flutter.io/cookbook/images/fading-in-images/
Am I possibly creating the stream and consequently the media instance incorrectly?
Modifying googleapis_examples/drive_upload_download_console, I'm attempting to convert already stored .docx, .xlsx, etc files to their corresponding Google Drive counterpart.
The following code
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' show Client;
import 'package:googleapis/common/common.dart' show Media, DownloadOptions;
import 'package:googleapis/drive/v2.dart' as drive;
import 'package:path/path.dart' as path;
Future convertFile(drive.DriveApi api,
Client client,
String objectId) {
var completer = new Completer();
api.files.get(objectId).then((drive.File file) {
var fileName = path.basenameWithoutExtension(file.title);
var parents = file.parents;
client.readBytes(file.downloadUrl).then((bytes) {
var driveFile = new drive.File()
..title = fileName
..mimeType = 'application/vnd.google-apps.document'
..parents = parents;
api.files.insert(driveFile)
.then((driveFile){
var byteList = bytes.toList();
var stream = new Stream.fromIterable(byteList);
var media = new Media(stream, byteList.length);
api.files.update(new drive.File(), driveFile.id, uploadMedia: media)
.then((drive.File f){
stream.close().whenComplete((){
api.files.delete(objectId)
.then((_){
completer.complete(true);
print("Converted ${f.id}");
});
});
});
});
});
});
return completer.future;
}
results in the following error.
Unhandled exception:
Uncaught Error: Class 'int' has no instance getter 'length'.
NoSuchMethodError: method not found: 'length'
Receiver: 80
Arguments: []
Stack Trace:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#1 Base64Encoder.bind.onData (package:googleapis/src/common_internal.dart:325:42)
#2 _RootZone.runUnaryGuarded (dart:async/zone.dart:1093)
#3 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341)
#4 _IterablePendingEvents.handleNext (dart:async/stream_impl.dart:549)
#5 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:671)
#6 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:41)
#7 _asyncRunCallback (dart:async/schedule_microtask.dart:48)
#8 _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:84)
#9 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:131)
#0 _rootHandleUncaughtError.<anonymous closure> (dart:async/zone.dart:886)
#1 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:41)
#2 _asyncRunCallback (dart:async/schedule_microtask.dart:48)
#3 _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:84)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:131)
The Media constructor expects a Stream<List<int>>, and your code is providing a Stream<int>. To solve this, just replace this line:
var stream = new Stream.fromIterable(byteList);
by:
var stream = new Stream.fromIterable([byteList]);