Flutter ImageStream Bad state: Future already completed - flutter

I have an WebView inside my app where I crawl the current Website.
This is the procedure:
User taps on button
crawl the content of the current URL
get all the images
for each image get its dimension
print out first three elements of sorted List
The Problem is 4:
This is my code for it:
Future<Size> _calculateImageDimension(String imageUrl) {
Completer<Size> completer = Completer();
Image image = Image.network(imageUrl);
image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
completer.complete(size); // <- StateError
},
),
);
return completer.future;
}
This fails with:
Bad state: Future already completed
Now the weird part is that it only fails on some URL's.
What is wrong with my _calculateImageDimension? What am I missing?
This is the complete Code:
import 'package:boilerplate/ui/shared_widgets/buttons/rounded_corner_text_button.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart' as dom;
class WebViewExample extends StatefulWidget {
#override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
// Reference to webview controller
late WebViewController _controller;
final _stopwatch = Stopwatch();
String _currentUrl = '';
List<ImageWithSize> _imagesWithSize = [];
bool _isLoading = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Web View Example'),
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: WebView(
initialUrl:
'https://www.prettylittlething.com/recycled-green-towelling-oversized-beach-shirt.html',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
// Get reference to WebView controller to access it globally
_controller = webViewController;
},
javascriptChannels: <JavascriptChannel>{
// Set Javascript Channel to WebView
_extractDataJSChannel(context),
},
onPageStarted: (String url) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (String url) {
setState(() {
_imagesWithSize = [];
_currentUrl = url;
_isLoading = false;
});
},
),
),
RoundedCornersTextButton(
title: 'GET',
isEnabled: !_isLoading,
onTap: () {
_getData();
}),
],
),
),
);
}
JavascriptChannel _extractDataJSChannel(BuildContext context) {
return JavascriptChannel(
name: 'Flutter',
onMessageReceived: (JavascriptMessage message) {
String pageBody = message.message;
},
);
}
void _getData() async {
// print(url);
_stopwatch.start();
final response = await http.get(Uri.parse(_currentUrl));
final host = Uri.parse(_currentUrl).host;
dom.Document document = parser.parse(response.body);
final elements = document.getElementsByTagName("img").toList();
for (var element in elements) {
var imageSource = element.attributes['src'] ?? '';
bool validURL = Uri.parse(imageSource).host == '' ||
Uri.parse(host + imageSource).host == ''
? false
: true;
if (validURL && !imageSource.endsWith('svg')) {
Uri imageSourceUrl = Uri.parse(imageSource);
if (imageSourceUrl.host.isEmpty) {
imageSource = host + imageSource;
}
if (_imagesWithSize.firstWhereOrNull(
(element) => element.imageUrl == imageSource,
) ==
null) {
Size size = await _calculateImageDimension(imageSource);
_imagesWithSize.add(
ImageWithSize(
imageSource,
size,
),
);
}
}
}
_imagesWithSize.sort(
(a, b) => (b.imageSize.height * b.imageSize.width).compareTo(
a.imageSize.height * a.imageSize.width,
),
);
print(_imagesWithSize.first.imageUrl);
print(_imagesWithSize[1].imageUrl);
print(_imagesWithSize[2].imageUrl);
_stopwatch.stop();
print('executed in ${_stopwatch.elapsed}');
}
}
Future<Size> _calculateImageDimension(String imageUrl) {
Completer<Size> completer = Completer();
Image image = Image.network(imageUrl);
image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
completer.complete(size);
},
),
);
return completer.future;
}
class ImageWithSize {
final String imageUrl;
final Size imageSize;
ImageWithSize(this.imageUrl, this.imageSize);
}

If the ImageStream emits more than once you will call completer.complete() twice, which is an error. As per the ImageStream documentation this can happen if the image is animating, or if the resource is changed.
If you only care about the first emit you can await image.image.resolve(ImageConfiguration()).first. A more hacky solution would be to call complete() only if completer.isCompleted == false.

Related

Flutter show dialog box if no "Internet Connection"

I'm beginner to Flutter development and im creating a webview app to load my responsive website into the app.
Everything works perfect but i need to show a dialog box saying "No internet connection" if there is no internet connection.
My code :
class _MyHomePageState extends State<MyHomePage> {
bool isLoading = true;
ConnectivityResult? _connectivityResult;
late StreamSubscription _connectivitySubscription;
bool? _isConnectionSuccessful;
#override
initState() {
super.initState();
_connectivitySubscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
print('Current connectivity status: $result');
setState(() {
_connectivityResult = result;
});
});
}
#override
dispose() {
super.dispose();
_connectivitySubscription.cancel();
}
Future<void> _checkConnectivityState() async {
final ConnectivityResult result = await Connectivity().checkConnectivity();
if (result == ConnectivityResult.wifi) {
print('Connected to a Wi-Fi network');
} else if (result == ConnectivityResult.mobile) {
print('Connected to a mobile network');
} else {
print(result);
}
setState(() {
_connectivityResult = result;
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
WebView(
javascriptMode: JavascriptMode.unrestricted,
// initialUrl: '',
onWebViewCreated: (WebViewController controller) async {
await WebviewCookieManager().setCookies([
Cookie('SESSION-Test', 'token')
..domain = 'dobuyme.online'
..httpOnly = true
]);
controller.loadUrl('https://example.com/source=web_view');
},
onPageFinished: (String url) {
setState(() {
isLoading = false;
});
},
// onPageFinished: (finish) {
// setState(() {
// var isLoading = false;
// });
// },
),
isLoading
? const Center(
child: CircularProgressIndicator(),
)
: Stack(),
],
);
}
}
This variable will be "true" if connection is successful
_isConnectionSuccessful
But i don't know how to append this with the webview and check connection before website loads.
I want to show a message that says "No intenet connection" and close the app.
Please anyone can help. me ?
Thanks
use internet_connection_checker: ^0.0.1+4 plugin to check if internet has available or not...
final StreamSubscription<InternetConnectionStatus> listener =
InternetConnectionChecker().onStatusChange.listen(
(InternetConnectionStatus status) {
switch (status) {
case InternetConnectionStatus.connected:
// Do what you want to do
break;
case InternetConnectionStatus.disconnected:
// Do what you want to do
break;
}
},
);
Here you you find the status....and show dailoge box when status is disconnected

i am trying to use starflut package in flutter but i am getting a lot of exceptions which i am unable to configure it

I am trying to import starflut package in flutter but i am getting a lot of exceptions which i am not able to solve.I have copy pasted the file according to (https://github.com/srplab/starcore_for_flutter).
Use this line to run the code in terminal :flutter run --no-sound-null-safety
enter image description here
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:starflut/starflut.dart';
import 'dart:async';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(home: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () async {
await initpla();
},
child: Container(
width: 350,
height: 500,
color: Colors.blue,
),
),
);
}
Future<void> initpla() async {
try {
StarCoreFactory starcore = await Starflut.getFactory();
StarServiceClass Service =
await starcore.initSimple("test", "123", 0, 0, []);
await starcore.regMsgCallBackP(
(int serviceGroupID, int uMsg, Object wParam, Object lParam) async {
print("$serviceGroupID $uMsg $wParam $lParam");
return null;
});
dynamic SrvGroup = await Service["_ServiceGroup"];
bool isAndroid = await Starflut.isAndroid();
if (isAndroid == true) {
//desRelatePath must be null
await Starflut.loadLibrary("libpython3.9.so");
await Starflut.copyFileFromAssets(
"python3.9.zip", "flutter_assets/starflut", null);
var nativepath = await Starflut.getNativeLibraryDir();
print("Nativepath : $nativepath");
var LibraryPath = "";
if (nativepath.contains("x86_64"))
LibraryPath = "x86_64";
else if (nativepath.contains("arm64"))
LibraryPath = "arm64";
else if (nativepath.contains("arm"))
LibraryPath = "armeabi";
else if (nativepath.contains("x86")) LibraryPath = "x86";
await Starflut.copyFileFromAssets(
"zlib.cpython-39.so", LibraryPath, null);
await Starflut.copyFileFromAssets(
"unicodedata.cpython-39.so", LibraryPath, null);
String docPath = await Starflut.getDocumentPath();
print("docPath = $docPath");
String resPath = await Starflut.getResourcePath();
print("resPath = $resPath");
dynamic rr1 = await SrvGroup.initRaw("python39", Service);
var Result = await SrvGroup.loadRawModule(
"python", "", resPath + "/" + "abc.py", false);
print("loadRawModulee = $Result");
dynamic python =
await Service.importRawContext(null, "python", "", false, "");
await SrvGroup.clearService();
await starcore.moduleExit();
String platformVersion = 'Python 3.9';
}
} catch (e) {
print("{$e.message}");
}
}
}

Riverpod future provider not rebuilding ui

My problem is that when I run the app, the data doesn't show up on the UI. The code below is rendered under a bottom navigation bar format which is a stateful widget. To my knowledge the below code should work (show data on the initial running of app).
The code works but the data is only shown when I press hot reload. I've tried everything that I know but it still doesn't show data when I start the app.
final imageControllerProvider = Provider((ref) {
return ImageController();
});
final mainScreenImages = FutureProvider<List<String>>((ref) async {
List<String> list = [];
list = await ref.watch(imageControllerProvider).getImages();
return list;
});
class ImageController{
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
reference.listAll().then((value) {
for (var element in value.items) {
element.getDownloadURL().then((e) => imageUrls.add(e));
}
});
} catch (e) {
print(e);
}
return imageUrls;
}
}
class GenerateImages extends ConsumerWidget {
const GenerateImages({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final imageList = ref.watch(mainScreenImages);
final double screenwidth = MediaQuery.of(context).size.width;
final double screenheight = MediaQuery.of(context).size.height;
return imageList.when(data: (data) {
return Text('$data');
}, error: (_, __) {
return const Scaffold(
body: Center(
child: Text("OOPS"),
),
);
}, loading: () {
return const Center(child: const CircularProgressIndicator());
});
}
}
I think the problem is because in getImages() you are not awaiting the results instead you are using the then() handler to register callbacks. Replace your getImages() function with this and try.
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
final value = await reference.listAll();
for (var element in value.items) {
final url = await element.getDownloadURL();
imageUrls.add(url);
}
} catch (e) {
print(e);
}
return imageUrls;
}
}

Riverpod - widget stays in loading state when listening to a streamProvider

I can't seem to get my ref.watch(mapProvider).when( to build the data case of my widget:
#override
Widget build(BuildContext context, WidgetRef ref) {
if (defaultTargetPlatform == TargetPlatform.android) {
AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
}
_vm = ref.watch(findPageVm);
_widgetRef = ref;
final isStillLoading = useState(true);
return ref.watch(findStoreProvider).when(
data: (store) {
_store = store;
store.$search();
return Stack(children: [
const VpLoadingPage(
noText: true,
),
AnimatedOpacity(
curve: Curves.fastOutSlowIn,
opacity: isStillLoading.value ? 0 : 1.0,
duration: GOOGLE_MAP_OVERLAY_DURATION,
child: Stack(children: [
ref.watch(mapProvider).when(
data: (snapshot) {
return GoogleMap(
markers: snapshot.markers,
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(_store!.userPosition.latitude,
_store!.userPosition.longitude),
zoom: snapshot.zoom,
),
onMapCreated: (GoogleMapController controller) {
_vm.controller.complete(controller);
Future.delayed(const Duration(milliseconds: 550),
() => isStillLoading.value = false);
},
);
},
error: (error, stackTrace) => Text(error.toString()),
loading: () {
return const VpLoadingPage(noText: true);
}),
mapOverlay()
]))
]);
},
error: (error, stackTrace) => Text(error.toString()),
loading: () => const VpLoadingPage(noText: true));
}
provider:
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:vepo/src/application/local/map_data/map.dart';
import 'package:vepo/src/domain/vegan_item_establishment/_common/vegan_item_establishment.dart';
import 'package:vepo/src/presentation/pages/find/search_results_provider.dart';
import 'package:vepo/src/presentation/stores/store.dart';
import 'package:vepo/src/presentation/stores/user_store.dart';
final findStoreProvider = FutureProvider((ref) async {
final userStore = await ref.watch(userStoreProvider.future);
final store = FindStore(ref, userStore: userStore);
await store.$search();
return store;
});
final mapProvider = StreamProvider<VpMap>((ref) async* {
final store = await ref.watch(findStoreProvider.future);
store.map$!.listen((event) {
inspect(event);
});
store.mapController$.add(VpMap.empty());
yield* store.map$!;
});
class FindStore extends Store {
factory FindStore(Ref $provider, {required UserStore userStore}) {
instance = FindStore._internal($provider, userStore);
instance.map$ = instance.mapController$.stream;
instance._userStore = userStore;
return instance;
}
FindStore._internal(this.$provider, this._userStore);
static late FindStore instance;
final Ref $provider;
Stream<VpMap>? map$;
VpMap? map;
late final StreamController<VpMap> mapController$ =
StreamController.broadcast();
late UserStore _userStore;
Set<Marker> markers = <Marker>{};
Stream<String?>? searchTerm;
Position get userPosition => _userStore.userPosition;
Future<List<VgnItmEst>> _$search(String searchTerm) async {
final result =
await $provider.watch(searchResultsProvider(searchTerm).future);
return result;
}
Set<Marker> _fromSearchResults(List<VgnItmEst> searchResults) {
return searchResults
.map((searchResult) => Marker(
markerId: MarkerId(searchResult.id.toString()),
position: LatLng(searchResult.establishment!.location!.lat,
searchResult.establishment!.location!.lng)))
.toSet();
}
Future<FindStore> $search([String searchTerm = '']) async {
final searchResults = await _$search(searchTerm);
markers = _fromSearchResults(searchResults);
map = VpMap(position: _userStore.userPosition, markers: markers);
mapController$.add(map!);
return this;
}
}
I've been trying to make FindStore a singleton as I thought it would be due to getting a different FindStore but it did not fix it. I have inspected the values being emitted with store.map$!.listen((event) { and there is one value emitted. Why does the widget remain in the loading state when reading mapProvider?

Compress or Resize image before rendering in flutter

I load all image directories and load all image paths in the directory that I loaded. (With storage_path)
I load images with the file path, so I use Image.file().
But, When I load big image files(4032×3024 size or >3MB), the loading is so laggy.
Like this:
I don't need to load images with high resolution because the image widget is small.
How can I compress or resize this images?
Or Is there any better solution?
Code:
// gallery_page.dart
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:listing/widget/bar_widget.dart';
import 'package:listing/provider/dark_mode_provider.dart';
import 'package:listing/page/sub/image/image_edit_page.dart';
import 'package:listing/page/sub/gallery/media_directory.dart';
import 'package:listing/util/image_types.dart';
import 'package:provider/provider.dart';
import 'package:storage_path/storage_path.dart';
enum GalleryType { IMAGE, VIDEO }
class GalleryPageArguments {
final GalleryType type;
GalleryPageArguments(this.type);
}
class GalleryPage extends StatelessWidget {
static final routeName = '/gallery_page';
#override
Widget build(BuildContext context) {
GalleryPageArguments arguments = ModalRoute.of(context).settings.arguments;
GalleryType type = arguments.type;
ColorPalette palette = Provider.of<DarkModeProvider>(context).colorPallette;
Future<List<MediaDirectory>> getPath;
if(type == GalleryType.IMAGE) {
getPath = _getImagePaths();
} else {
getPath = _getVideoPaths();
}
return Scaffold(
body: SafeArea(
child: FutureBuilder<List<MediaDirectory>>(
future: getPath,
builder: (context, snapshot) {
if(snapshot.hasData) {
HashMap<String, MediaDirectory> mediaDirsMap = new HashMap<String, MediaDirectory>();
List<MediaDirectory> mediaDirs = snapshot.data;
for(MediaDirectory mediaDir in mediaDirs) {
mediaDirsMap.putIfAbsent(mediaDir.dirName, () => mediaDir);
}
return _InnerGalleryPage(palette, mediaDirsMap);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
),
);
}
Future<List<MediaDirectory>> _getImagePaths() async {
String imagesPath = await StoragePath.imagesPath;
dynamic response = jsonDecode(imagesPath);
List<dynamic> imageDirJsonList = List.from(response);
List<MediaDirectory> imageDirList = imageDirJsonList.map((imageDirJson) => MediaDirectory.fromJson(imageDirJson, GalleryType.IMAGE)).toList();
return imageDirList;
}
Future<List<MediaDirectory>> _getVideoPaths() async {
String videoPath = await StoragePath.videoPath;
dynamic response = jsonDecode(videoPath);
List<dynamic> videoDirJsonList = List.from(response);
List<MediaDirectory> imageDirList = videoDirJsonList.map((imageDirJson) => MediaDirectory.fromJson(imageDirJson, GalleryType.VIDEO)).toList();
return imageDirList;
}
}
class _InnerGalleryPage extends StatefulWidget {
final ColorPalette _palette;
final HashMap<String, MediaDirectory> _mediaDirs;
_InnerGalleryPage(this._palette, this._mediaDirs);
#override
_InnerGalleryPageState createState() => _InnerGalleryPageState();
}
class _InnerGalleryPageState extends State<_InnerGalleryPage> {
String currentSelectedDir;
#override
void initState() {
super.initState();
String firstDir = widget._mediaDirs.keys.toList()[0];
currentSelectedDir = widget._mediaDirs[firstDir].dirName;
}
#override
Widget build(BuildContext context) {
HashMap<String, MediaDirectory> mediaDirs = widget._mediaDirs;
return BarWidget(
titleWidget: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: currentSelectedDir,
items: mediaDirs.keys.map(
(mediaDirName) {
return DropdownMenuItem(
value: mediaDirName,
child: Text(mediaDirName),
);
}
).toList(),
onChanged: (changedDir) {
setState(() {
currentSelectedDir = changedDir;
});
},
)
),
palette: widget._palette,
child: Container(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
crossAxisSpacing: 5,
mainAxisSpacing: 5
),
itemBuilder: (context, index) {
String imagePath = mediaDirs[currentSelectedDir].mediaFiles[index].path;
ImageTypes imageType = getImageTypesFromPath(imagePath);
return GestureDetector(
child: _getImage(imagePath),
onTap: () {
if(imageType == ImageTypes.GIF) {
File imageFile = File(imagePath);
Image pickedImage = Image.file(imageFile);
Navigator.pop(context, pickedImage);
} else {
File imageFile = File(imagePath);
Image pickedImage = Image.file(imageFile);
Navigator.pushNamed(context, ImageEditPage.routeName, arguments: ImageEditPageArguments(pickedImage, imagePath, imageType)).then((result) {
if(result is Image) {
Navigator.pop(context, result);
}
});
}
}
);
},
itemCount: mediaDirs[currentSelectedDir].mediaFilesCount,
)
),
);
}
Image _getImage(String filePath) {
File imageFile = File(filePath);
Image image = Image.file(imageFile, fit: BoxFit.cover);
return image;
}
}
// media_directory.dart
import 'dart:convert';
import 'package:listing/page/sub/gallery/gallery_page.dart';
import 'package:listing/page/sub/gallery/media_file.dart';
class MediaDirectory {
final String _dirName;
final List<MediaFile> _mediaFiles;
String get dirName => _dirName;
int get mediaFilesCount => _mediaFiles.length;
List<MediaFile> get mediaFiles => _mediaFiles;
MediaDirectory(this._dirName, this._mediaFiles);
factory MediaDirectory.fromJsonString(String jsonString, GalleryType type) {
return MediaDirectory.fromJson(jsonDecode(jsonString), type);
}
factory MediaDirectory.fromJson(dynamic json, GalleryType type) {
String dirName = json['folderName'];
List<MediaFile> mediaFiles = [];
if(type == GalleryType.IMAGE) {
List<String> paths = List.from(json['files']);
mediaFiles = paths.map((path) => ImageFile(path)).toList();
} else if(type == GalleryType.VIDEO) {
List<dynamic> files = List.from(json['files']);
mediaFiles = files.map((fileJson) => VideoFile.fromJson(fileJson)).toList();
}
return MediaDirectory(dirName, mediaFiles);
}
}
// media_file.dart
import 'dart:convert';
import 'package:flutter/material.dart';
class MediaFile {
final String _path;
String get path => _path;
MediaFile(this._path);
}
class ImageFile extends MediaFile {
ImageFile(String path) : super(path);
}
class VideoFile extends MediaFile {
final String _fileName;
final int _addedDate;
final int _duration;
final int _size;
String get fileName => _fileName;
int get addedDate => _addedDate;
int get duration => _duration;
int get size => _size;
VideoFile({
#required String path,
#required String fileName,
#required int addedDate,
#required int duration,
#required int size
})
: assert(path != null),
assert(fileName != null),
assert(addedDate != null),
assert(duration != null),
assert(size != null),
this._fileName = fileName,
this._addedDate = addedDate,
this._duration = duration,
this._size = size,
super(path);
factory VideoFile.fromJsonString(String jsonString) {
return VideoFile.fromJsonString(jsonDecode(jsonString));
}
factory VideoFile.fromJson(dynamic json) {
String path = json['path'];
String fileName = json['displayName'];
int addedDate = int.parse(json['dateAdded']);
int duration = int.parse(json['duration']);
int size = int.parse(json['size']);
return VideoFile(path: path, fileName: fileName, addedDate: addedDate, duration: duration, size: size);
}
}
If you need other code to solve this problem, I can give you code that you need.
Thanks.
You should load grid image with thumbnail, not actual image. Try to use this, photo_manager:
either get paged:
final assetList = await path.getAssetListPaged(page, perPage);
or ranged:
final assetList = await path.getAssetListRange(start: 0, end: 88);
After that, get the entity.thumbData to show thumbnail:
Uint8List thumbBytes = await entity.thumbData;