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;
Related
I would like to retrieve the distance between two positions.
I can get the name but not the distance. I have an error
The following _CastError was thrown building:
Null check operator used on a null value
when I do
print(jsonData);
I get
I/flutter ( 8181): [{nom: CLINIQUE CENTRE, latitude: 37.7586968, longitude: -122.3053474, distance: 0}, {nom: CLINIQUE CHATEAU, latitude: 37.8711583, longitude: -122.336457, distance: 0}, {nom: CLINIQUE ARAFAT, latitude: 37.5206515, longitude: -122.064364, distance: 0}]
this is my model
class Destination {
double lat;
double lng;
String name;
double? distance;
Destination(this.lat, this.lng, this.name, {this.distance});
}
here is what i tried
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_sorting_location/Utils.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'Destinations.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
double? distance;
List<Destination> destinations = [];
Position? _currentPosition;
Future<List<Destination>> getData() async {
var url = 'http://xxxx/flutter/getlocation.php';
var res = await http.get(Uri.parse(url));
var jsonData = json.decode(res.body);
print(jsonData);
for (var destinationval in jsonData) {
// print(destinationval['nom']);
Destination dests = Destination(
double.parse(destinationval['latitude']),
double.parse(destinationval['longitude']),
destinationval['nom'],
);
destinations.add(dests);
}
// print(destinations);
return destinations;
}
#override
void initState() {
_getCurrentLocation();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Location sorting from current location"),
),
body: FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final audioList = snapshot.data as List<Destination>;
return ListView.builder(
itemCount: audioList.length,
itemBuilder: (context, index) {
return Card(
margin: EdgeInsets.all(5),
elevation: 5,
child: Padding(
padding: EdgeInsets.all(5),
child: Container(
height: 40,
color: Colors.white,
child: Column(
children: [
Text(audioList[index].name.toString()),
Text(
"${audioList[index].distance!.toStringAsFixed(2)} km"),
],
),
),
),
);
});
} else if (snapshot.hasError) {
// handle error here
return Text('${snapshot.error}');
} else {
return CircularProgressIndicator(); // displays while loading data
}
}),
);
}
// get Current Location
_getCurrentLocation() {
Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.best,
forceAndroidLocationManager: true)
.then((Position position) {
distanceCalculation(position);
setState(() {
_currentPosition = position;
});
}).catchError((e) {
print(e);
});
}
distanceCalculation(Position position) {
for (var d in destinations) {
print(d);
var km = getDistanceFromLatLonInKm(
position.latitude, position.longitude, d.lat, d.lng);
// var m = Geolocator.distanceBetween(position.latitude,position.longitude, d.lat,d.lng);
// d.distance = m/1000;
d.distance = km;
destinations.add(d);
// print(getDistanceFromLatLonInKm(position.latitude,position.longitude, d.lat,d.lng));
}
setState(() {
destinations.sort((a, b) {
// print("a : ${a.distance} b : ${b.distance}");
return a.distance!.compareTo(b.distance!);
});
});
}
}
While you are creating instance, it is missing optional distance field here.
for (var destinationval in jsonData) {
Destination dests = Destination(
double.parse(destinationval['latitude']),
double.parse(destinationval['longitude']),
destinationval['nom'],
distance: double.tryParse(destinationval["distance"])
);
audioList[index].distance is getting null, while distance is nullable, it is risky to use ! without null check.
Safer way is checking null 1st and then use ! or provide default value on null case. For string on Text, it can print null
Text("${audioList[index].distance?.toStringAsFixed(2)} km"),
Or just ignore the text build like
if(audioList[index].distance!=null) Text("${audioList[index].distance?.toStringAsFixed(2)} km"),
with
for (var destinationval in jsonData) {
Destination dests = Destination(
double.parse(destinationval['latitude']),
double.parse(destinationval['longitude']),
destinationval['nom'],
distance: double.tryParse(destinationval["distance"])
)
and
if(audioList[index].distance!=null) Text("${audioList[index].distance?.toStringAsFixed(2)} km"),
the screen now displays
type 'Null' is not a subtype of type 'String'
I think the problem lies here since I have to calculate the distance and display it
distanceCalculation(Position position) {
for (var d in destinations) {
var km = getDistanceFromLatLonInKm(
position.latitude, position.longitude, d.lat, d.lng);
d.distance = km;
destinations.add(d);
// print(getDistanceFromLatLonInKm(position.latitude,position.longitude, d.lat,d.lng));
}
setState(() {
destinations.sort((a, b) {
// print("a : ${a.distance} b : ${b.distance}");
return a.distance!.compareTo(b.distance!);
});
});
}
I just corrected
Text("${audioList[index].distance?.toStringAsFixed(2)} km"),
but i can't get distance . it's show me null
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}");
}
}
}
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?
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.
I'm trying to display a pdf using this button in my flutter app but I keep getting the error in the title
AppButton.buildAppButton(
context,
AppButtonType.TEXT_OUTLINE,
'Generate PDF',
Dimens.BUTTON_BOTTOM_DIMENS, onPressed: () {
Sheets.showAppHeightEightSheet(
context: context,
widget: work(),
);
})
This is my work widget
Widget work() {
dynamic pdf = generateInvoice(PdfPageFormat.a4);
return pdf;
}
which calls this function. However I keep getting the title error type 'Future' is not a subtype of type 'Widget'. Any help is appreciated. end goal is to view the pdf but I'm not sure how ti get there. Thanks!
Future<Uint8List> generateInvoice(PdfPageFormat pageFormat) async {
final lorem = pw.LoremText();
final products = <Product>[
Product('19874', lorem.sentence(4), 3.99, 2),
Product('98452', lorem.sentence(6), 15, 2),
Product('28375', lorem.sentence(4), 6.95, 3),
Product('95673', lorem.sentence(3), 49.99, 4),
Product('23763', lorem.sentence(2), 560.03, 1),
Product('55209', lorem.sentence(5), 26, 1),
Product('09853', lorem.sentence(5), 26, 1),
];
final invoice = Invoice(
invoiceNumber: '982347',
products: products,
customerName: 'Abraham Swearegin',
customerAddress: '54 rue de Rivoli\n75001 Paris, France',
paymentInfo:
'4509 Wiseman Street\nKnoxville, Tennessee(TN), 37929\n865-372-0425',
tax: .15,
baseColor: PdfColors.teal,
accentColor: PdfColors.blueGrey900,
);
return await invoice.buildPdf(pageFormat);
}
class Invoice {
Invoice({
this.products,
this.customerName,
this.customerAddress,
this.invoiceNumber,
this.tax,
this.paymentInfo,
this.baseColor,
this.accentColor,
});
final List<Product> products;
final String customerName;
final String customerAddress;
final String invoiceNumber;
static const _darkColor = PdfColors.blueGrey800;
static const _lightColor = PdfColors.white;
PdfColor get _baseTextColor =>
baseColor.luminance < 0.5 ? _lightColor : _darkColor;
PdfColor get _accentTextColor =>
baseColor.luminance < 0.5 ? _lightColor : _darkColor;
double get _total =>
products.map<double>((p) => p.total).reduce((a, b) => a + b);
double get _grandTotal => _total * (1 + tax);
PdfImage _logo;
Future<Uint8List> buildPdf(PdfPageFormat pageFormat) async {
// Create a PDF document.
final doc = pw.Document();
final font1 = await rootBundle.load('assets/roboto1.ttf');
final font2 = await rootBundle.load('assets/roboto2.ttf');
final font3 = await rootBundle.load('assets/roboto3.ttf');
_logo = PdfImage.file(
doc.document,
bytes: (await rootBundle.load('assets/logo.png')).buffer.asUint8List(),
);
// Add page to the PDF
doc.addPage(
pw.MultiPage(
pageTheme: _buildTheme(
pageFormat,
font1 != null ? pw.Font.ttf(font1) : null,
font2 != null ? pw.Font.ttf(font2) : null,
font3 != null ? pw.Font.ttf(font3) : null,
),
header: _buildHeader,
footer: _buildFooter,
build: (context) => [
_contentHeader(context),
_contentTable(context),
pw.SizedBox(height: 20),
_contentFooter(context),
pw.SizedBox(height: 20),
_termsAndConditions(context),
],
),
);
// Return the PDF file content
return doc.save();
}
EDIT:: I'm trying to do this now but i get a red underline under the commented line
class MyWidget extends StatelessWidget {
#override
Widget build(context) {
return FutureBuilder<Uint8List>(
future: generateInvoice(PdfPageFormat.a4),
builder: (context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return snapshot.data; //get a red underline here
} else {
return CircularProgressIndicator();
}
}
);
}
}
Your work function should look like this. Here two extra package I have used. For showing pdf flutter_pdfview and to save the pdf temporary path_provider.
Future<Widget> work() async{
Uint8List pdf = await generateInvoice(PdfPageFormat.a4);
File file = await getPdf(pdf);
return PDFView(
filePath: file.path,
autoSpacing: false,
pageFling: false,
);
}
EDITED:
Future<File> getPdf(Uint8List pdf) async{
Directory output = await getTemporaryDirectory();
file = File(output.path+"/name_of_the_pdf.pdf");
await file.writeAsBytes(pdf);
return file;
}
Now update your build method as
#override
Widget build(context) {
return FutureBuilder<Widget>(
future: work(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return snapshot.data; //get a red underline here
} else {
return CircularProgressIndicator();
}
}
);
}