how can I show image picked by file_picker in web while the file path is null in web platform ?
If the path was not null, showing the image is too easy with Image.file(File):
Image.file(context.select<BlogProvider, File>((BlogProvider p) => p.image))
but It can not create File for image in web because browsers don't give file path and It's null.
Future<void> pickImage() async {
/// If [withReadStream] is set, picked files will have its byte data available as a [Stream<List<int>>]
/// which can be useful for uploading and processing large files.
FilePickerResult result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg'],
withReadStream: true,
);
if (result != null) {
PlatformFile file = result.files.single; //single means I am Picking just One file not more
_blogImage = File(file.path);//Null in web ,but Ok in Android
notifyListeners();
} else {
// User canceled the picker
}
}
When withReadStream is set to true, selected image can be accessed as:
file.readStream.listen((event) {
_blogImage = Image.memory(event);
notifyListeners();
});
but when withReadStream is false:
_blogImage = Image.memory(file.bytes);
notifyListeners();
And although file.path is null in flutter for web, the file.name is set correctly and we can display it.
More info here
Another way (without file_picker package):
import 'dart:html' as html;
// ...
void pickFile() {
final input = html.FileUploadInputElement()..accept = 'image/*';
input.onChange.listen((event) {
if (input.files.isNotEmpty) {
fileName = input.files.first.name; // file name without path!
// synthetic file path can be used with Image.network()
url = html.Url.createObjectUrl(input.files.first);
});
}
});
input.click();
}
You can use Image.memory()
an exemple using the package universal_html
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: DemoApp0(),
),
),
);
}
class DemoApp0 extends StatefulWidget {
DemoApp0({
Key key,
}) : super(key: key);
#override
_DemoApp0State createState() => _DemoApp0State();
}
class _DemoApp0State extends State<DemoApp0> {
final Map<String, Uint8List> files = {};
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
TextButton(
onPressed: ()=>pickWebFile(),
child: Text("select file"),
),
Column(
children: files.entries
.map((e) => Column(
children: [
Text(e.key),
SizedBox(
width: 200,
height: 300,
child: Image.memory(e.value),
)
],
))
.toList(),
)
],
),
);
}
Future<void> pickWebFile() async {
List<html.File> webFiles = [];
html.InputElement uploadInput = html.FileUploadInputElement();
uploadInput.click();
uploadInput.onChange.listen((e) {
webFiles = uploadInput.files;
for (html.File webFile in webFiles) {
var r = new html.FileReader();
Uint8List fileData;
r.readAsArrayBuffer(webFile);
r.onLoadEnd.listen((e) async {
fileData = r.result;
if (webFile.size < 4194304) {
setState(() {
files[webFile.name] = fileData;
});
}
});
}
});
}
}
Related
I have the in-memory bytes of a decrypt epub file, but the package that I want to open this epub file with only accepts dart:io File objects.
I don't want to create real local file => just for security
Is there a way to create a "fake" dart:io File, simply wrapping my in-memory bytes, so that I can pass this "fake" File to the package?
I'm using vocsy_epub_viewer package to open epub files
filePath should be a local file
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:vocsy_epub_viewer/epub_viewer.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:encrypt/encrypt.dart' as en;
import 'dart:io';
import 'package:cross_file/cross_file.dart';
import 'package:file/memory.dart';
import 'package:file/file.dart' as F;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Ebook(),
);
}
}
class Ebook extends StatefulWidget {
#override
State<Ebook> createState() => _EbookState();
}
class _EbookState extends State<Ebook> {
Directory? _temp;
String? path;
#override
void initState() {
address();
super.initState();
}
Future decodeEpub(String path) async {
final bytes = await File(path).readAsBytes();
Archive archive =
await ZipDecoder().decodeBytes(bytes, password: '7web', verify: true);
final data = await archive.first.content;
final key = en.Key.fromUtf8('qwertyuiopasdfghjklmnbvcxzasdfgh');
final iv = en.IV.fromLength(16);
final decrypted = await en.AES(key).decrypt(en.Encrypted(data), iv: iv);
// XFile file = await XFile.fromData(decrypted,name:'2.epub',path:"${_appDocumentsDirectory!.path}/2.epub" );
// File file1= await File(file.path).create(recursive: true);
// File file = MemoryFileSystem().file('${temp!.path}/2.epub')..create(recursive: true)
// ..writeAsBytesSync(decrypted);
File file = await File('${_temp!.path}/2.epub').writeAsBytes(decrypted); // it must be change ******
print(file.path);
return file.path;
}
void address() async {
final temp = await getTemporaryDirectory();
setState(() {
_temp = temp;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
FilePickerResult? result =
await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path!);
path = await decodeEpub(file.path);
print('?= $path , ${file.path}');
} else {
print('cancel');
}
},
child: Text('add File')),
ElevatedButton(
onPressed: () {
EpubViewer.setConfig(
themeColor: Theme.of(context).primaryColor,
identifier: "iosBook",
scrollDirection: EpubScrollDirection.ALLDIRECTIONS,
allowSharing: true,
enableTts: true,
nightMode: false);
EpubViewer.open(path!, lastLocation: EpubLocator());
},
child: Text("open"),
),
],
),
),
);
}
}
I don't want to create real local file => just for security
This is not actually true because EPUBs are structured as a zip file and folioreader, the framework used behind the vocsy_epub_viewer, will "unzip" the file to a temp folder (or one predefined). So, in the end, it's not going to be secure anyway.
Just extract the EPUB to the temp folder from path_provider and remove it afterwards. Otherwise, you're going to need a new framework to read EPUBs that don't have this behaviour or even customize the vocsy_epub_viewer/folioreader sources yourself to make it secure.
My app should pick a directory and list the files of the directory in cards.
Right now it does that but it has a little inconvenience, it lists the files from the "previous" directory.
For example, if the first picked dir is D:\AM it would list the files at the default path location (in my code an empty String), but if I press the button again and choose a different directory like D:\AM\test (expecting a single text file) it would list the files of D:\AM. I change the app title to match the picked directory at the button press but as you can see it lists the files from another directory (previous picked directory D:\AM).
What I get:
What I should get from the start (got this after picking the D:\AM\test dir again):
My code:
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'dart:io';
class Demo3 extends StatefulWidget {
const Demo3({Key? key}) : super(key: key);
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
late String path = '';
var files = [];
void selectDir() async {
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
if (selectedDirectory == null) {
setState(() {
path = "";
});
}
else{
setState(() {
path = selectedDirectory;
});
}
print(path);
}
void listFiles(String paths) async {
var dir = Directory(paths);
List<FileSystemEntity> entities = await dir.list().toList();
setState(() {
files = entities;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$path'),
actions: [
IconButton(
onPressed: () {
selectDir();
listFiles(path);
},
icon: const Icon(Icons.browse_gallery),
),
]
),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3
),
itemCount: files.length,
itemBuilder:(BuildContext context, int index){
var test = files[index];
return Card(
child: Text('$test'),
);
}
)
);
}
}
Any help is appreciated, thanks!
try this, just replaced listFiles() inside selectDir rather than in button tap.
void selectDir() async {
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
print(selectedDirectory);
if (selectedDirectory == null) {
setState(() {
path = "";
});
} else {
setState(() {
path = selectedDirectory;
listFiles(path);
});
}
print(path);
}
I am using flutter localizations for changing language in my flutter app. I want to change my app's language in real time and have implemented logic for that. Now, I want that when user closes app and restarts it, he gets same language he chose before, i.e. language should not set back to default after user closes the app. For this purpose, I was using shared preferences to save the code of language that user selected and then retrieve it in the beginning of the app.
app_locale.dart -
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AppLocale extends ChangeNotifier {
Locale? _locale;
Locale get locale => _locale ?? Locale('en');
void getLocale() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String code = prefs.getString("code")??"en";
Locale newLocale = Locale(code);
if(newLocale == Locale('bn')) {
_locale = Locale('bn');
} else if(newLocale==Locale('gu')){
_locale = Locale('gu');
} else if(newLocale==Locale('en')){
_locale = Locale('en');
} else if(newLocale==Locale('pa')){
_locale = Locale('pa');
}
}
void changeLocale(Locale newLocale) async {
if(newLocale == Locale('bn')) {
_locale = Locale('bn');
} else if(newLocale==Locale('gu')){
_locale = Locale('gu');
} else if(newLocale==Locale('en')){
_locale = Locale('en');
} else if(newLocale==Locale('pa')){
_locale = Locale('pa');
}
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString("code", _locale?.countryCode??"en");
notifyListeners();
}
}
I am calling getLocale in main.dart -
class MyApp extends StatelessWidget {
GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey=GlobalKey<ScaffoldMessengerState>();
Locale? defaultLanguage;
#override
Widget build(BuildContext context) {
var language = Provider.of<AppLocale>(context);
language.getLocale();
return Consumer<AppLocale>(
........
........
And in my language selection screen, I am changing language like this -
var language = Provider.of<AppLocale>(context);
child: Column(
children: [
LanguageTile(
shortForm: "Pa",
fullName: "ਪੰਜਾਬੀ",
isSelected: selectedLanguage==0,
onTap: () {
changeSelectedLanguage(0);
language.changeLocale(Locale('pa'));
},
),
LanguageTile(
shortForm: "GU",
fullName: "ગુજરાતી",
isSelected: selectedLanguage==1,
onTap: () {
changeSelectedLanguage(1);
language.changeLocale(Locale('gu'));
},
),
LanguageTile(
shortForm: "বা",
fullName: "বাংলা",
isSelected: selectedLanguage==2,
onTap: () {
changeSelectedLanguage(2);
language.changeLocale(Locale('bn'));
},
),
LanguageTile(
shortForm: "A",
fullName: "English",
isSelected: selectedLanguage==3,
onTap: () {
changeSelectedLanguage(3);
language.changeLocale(Locale('en'));
},
),
//Text(AppLocalizations.of(context)!.helloWorld),
],
),
Please someone guide me for this.
Heading
You have to call the get language method in initState. Or show a loading or pop up while the data is loading in background. Sometimes it happens because data is not loaded yet and build context already create the screen and the ui. I hope this will work.
the data is not coming because when loading data from
SharedPreferences it take time.so method is not in void it in Future.
please paste the below code
Future getLocale() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String code = prefs.getString("code")??"en";
Locale newLocale = Locale(code);
if(newLocale == Locale('bn')) {
_locale = Locale('bn');
} else if(newLocale==Locale('gu')){
_locale = Locale('gu');
} else if(newLocale==Locale('en')){
_locale = Locale('en');
} else if(newLocale==Locale('pa')){
_locale = Locale('pa');
}
}
class MyApp extends StatelessWidget {
GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey=GlobalKey<ScaffoldMessengerState>();
Locale? defaultLanguage;
var language;
void initmethod(context)async
{
language = await Provider.of<AppLocale>(context).getLocale();
}
#override
Widget build(BuildContext context) {
initmethod(context);
return Consumer<AppLocale>(
........
........
why yout dont use ListView().builder or Grid().builder to habe less code and use a list with all entries and les
child: ListView.builder(
itemCount: list.length,
itemBuilder: (context, i){
return LanguageTile(
shortForm: list[i].shortForm,
fullName: list[i].fullName,
onSelected: (value) {
changeSelectedLanguage(value);
language.changeLocale(Locale('en'));
},
),
}
},
),
I wrote this function to save contact number, but it can't save on local storage
Future _saveContact() async {
Contact contact = Contact();
contact.familyName = 'FreeZone';
contact.phones = [Item(label: "mobile", value: '01752591591')];
contact.emails = [Item(label: "work", value: 'info#34.71.214.132')];
if (await Permission.contacts.request().isGranted) {
await ContactsService.addContact(contact);
print("Contact added successfully");
return contact;
}
}
dependencies:
contacts_service: ^0.6.3
permission_handler: ^8.3.0
How to save contact according to the above-given Name, Number, Email?
I could see 2 plugins in pub.dev that can do this for you in Android and iOS.
flutter_contact - A Flutter plugin to retrieve, create and save contacts and contact-related events on Android and iOS devices.
contacts_service - A Flutter plugin to retrieve and manage contacts on Android and iOS devices.
Please have a look into them.
Add this :
dependencies:
contacts_service: ^0.6.3
then:
import 'package:contacts_service_example/contacts_list_page.dart';
import 'package:contacts_service_example/contacts_picker_page.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() => runApp(ContactsExampleApp());
// iOS only: Localized labels language setting is equal to CFBundleDevelopmentRegion value (Info.plist) of the iOS project
// Set iOSLocalizedLabels=false if you always want english labels whatever is the CFBundleDevelopmentRegion value.
const iOSLocalizedLabels = false;
class ContactsExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
routes: <String, WidgetBuilder>{
'/add': (BuildContext context) => AddContactPage(),
'/contactsList': (BuildContext context) => ContactListPage(),
'/nativeContactPicker': (BuildContext context) => ContactPickerPage(),
},
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
_askPermissions(null);
}
Future<void> _askPermissions(String routeName) async {
PermissionStatus permissionStatus = await _getContactPermission();
if (permissionStatus == PermissionStatus.granted) {
if (routeName != null) {
Navigator.of(context).pushNamed(routeName);
}
} else {
_handleInvalidPermissions(permissionStatus);
}
}
Future<PermissionStatus> _getContactPermission() async {
PermissionStatus permission = await Permission.contacts.status;
if (permission != PermissionStatus.granted &&
permission != PermissionStatus.permanentlyDenied) {
PermissionStatus permissionStatus = await Permission.contacts.request();
return permissionStatus;
} else {
return permission;
}
}
void _handleInvalidPermissions(PermissionStatus permissionStatus) {
if (permissionStatus == PermissionStatus.denied) {
final snackBar = SnackBar(content: Text('Access to contact data denied'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else if (permissionStatus == PermissionStatus.permanentlyDenied) {
final snackBar =
SnackBar(content: Text('Contact data not available on device'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Contacts Plugin Example')),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
child: const Text('Contacts list'),
onPressed: () => _askPermissions('/contactsList'),
),
ElevatedButton(
child: const Text('Native Contacts picker'),
onPressed: () => _askPermissions('/nativeContactPicker'),
),
],
),
),
);
}
}
I think I solved your problem.
_saveContact () async {
// 'without Future' is working
var newPerson = Contact();
// newPerson uses Contact Package
newPerson.givenName = 'FreeZone';
newPerson.phones = [Item(label: "mobile", value: '01752591591')];
newPerson.emails = [Item(label: "work", value: 'info#34.71.214.132')];
if (await Permission.contacts.status.isGranted) {
await ContactsService.addContact(newPerson);
var contacts = await ContactsService.getContacts();
print("Contact added successfully");
return contacts;
// setState(() {
// //setState isn't necessary, it just shows 'contact' directly on a screen.
// name = contacts;
// // I put 'contacts' in 'name' directly
// });
}
}
Actually, I was in trouble using 'newPerson.phones'.
I was wondering how to put my parameter in 'phone number'.
However, with your code, I could know how to write the code.
Thank you and please accept this answer as a small token of my appreciation.
And it is what I wrote you helped.
addPerson (given,family,number) async {
var newPerson = Contact();
newPerson.givenName = given;
newPerson.familyName = family;
newPerson.phones = [Item(label: "mobile", value: number)];
// I wrote 'newPerson.phones = [number];' and it was wrong.
await ContactsService.addContact(newPerson);
// adding newPerson
var contacts = await ContactsService.getContacts();
// call all of contacts
setState(() {
name = contacts;
});
// to show the contacts directly, I use 'setState'.
}
In my Flutter project, I want to download some files as zip and then unzip it programmatically and save it in device locally. So, for that reason I followed some examples, here's the code for that-
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class DownloadAssetsDemo extends StatefulWidget {
DownloadAssetsDemo() : super();
final String title = "Download & Extract ZIP Demo";
#override
DownloadAssetsDemoState createState() => DownloadAssetsDemoState();
}
class DownloadAssetsDemoState extends State<DownloadAssetsDemo> {
//
bool _downloading;
String _dir;
List<String> _images, _tempImages;
String _zipPath = 'https://coderzheaven.com/youtube_flutter/images.zip';
String _localZipFileName = 'images.zip';
#override
void initState() {
super.initState();
_images = List();
_tempImages = List();
_downloading = false;
_initDir();
}
_initDir() async {
if (null == _dir) {
_dir = (await getApplicationDocumentsDirectory()).path;
}
}
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
return file.writeAsBytes(req.bodyBytes);
}
Future<void> _downloadZip() async {
setState(() {
_downloading = true;
});
_images.clear();
_tempImages.clear();
var zippedFile = await _downloadFile(_zipPath, _localZipFileName);
await unarchiveAndSave(zippedFile);
setState(() {
_images.addAll(_tempImages);
_downloading = false;
});
}
unarchiveAndSave(var zippedFile) async {
var bytes = zippedFile.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes);
for (var file in archive) {
var fileName = '$_dir/${file.name}';
if (file.isFile) {
var outFile = File(fileName);
//print('File:: ' + outFile.path);
_tempImages.add(outFile.path);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
}
buildList() {
return Expanded(
child: ListView.builder(
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
return Image.file(
File(_images[index]),
fit: BoxFit.fitWidth,
);
},
),
);
}
progress() {
return Container(
width: 25,
height: 25,
padding: EdgeInsets.fromLTRB(0.0, 20.0, 10.0, 20.0),
child: CircularProgressIndicator(
strokeWidth: 3.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
_downloading ? progress() : Container(),
IconButton(
icon: Icon(Icons.file_download),
onPressed: () {
_downloadZip();
},
),
],
),
body: Container(
child: Column(
children: <Widget>[
buildList(),
],
),
),
);
}
}
This example, works fine with all the functionalities- zip file download, extract the file and load the images.
But the problem is
When I want to download the file from my desired location where I have saved a sqlite database(Size:19 mb) as a zip file, it doesn't work like the way it happened for the given code.
It shows the following error exception-
[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: FormatException: Could not find End of Central Directory Record
And I am not exactly getting whether the problem is in my download path or I need to make some changes in my coding example?
So, I need some suggestion to fix this exception and download and unzip my desired file from desired url.
This was likely caused by the fact that the file was not yet flushed to the filesystem after downloading before attempting to extract same.
To fix this update the _downloadFile method to the following
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
return file.writeAsBytes(req.bodyBytes, flush: true); // Added flush: true
}
From dart:io docs
Future<File> writeAsBytes(List<int> bytes, {FileMode mode = FileMode.write, bool flush = false})
Writes a list of bytes to a file.
Opens the file, writes the list of bytes to it, and closes the file. Returns a Future<File> that completes with this [File] object once the entire operation has completed.
By default [writeAsBytes] creates the file for writing and truncates the file if it already exists. In order to append the bytes to an existing file, pass [FileMode.append] as the optional mode parameter.
Note: --> If the argument [flush] is set to true, the data written will be flushed to the file system before the returned future completes.