Flutter: Gridview not updating with expected values - flutter

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);
}

Related

Random parameter for FutureBuilder

Here I have a StatefulWidget in which I want to get a random pet each time from a random url. Also, I have a condition for the random pet, if the condition is true, the pet will be shown, otherwise the random url and random pet should be selected again. I attached my code below, and the problem is the url only changes when the condition is false, but I want it to be randomly selected each time.
Putting the API.get_pets(init_random_url); in the future parameter of the FutureBuilder will solve the random selection but if the condition is false the URL and the pet would change two or three times, after searching about it and reading FutureBuilder documentation I put it in the initState and requestAgain and build, but I recognized the selectedURL in the build function does not work and the widget is stucked in the same URL until the condition gets false value.
import 'dart:developer';
import 'package:double_back_to_close/toast.dart';
import 'package:flutter/material.dart';
import 'package:pet_store/widgets/guess_game_random_card.dart';
import 'webservice/API.dart';
import 'main.dart';
import 'dart:math';
import 'utils/utils.dart';
Random random = new Random();
class Guess_Game extends StatefulWidget {
const Guess_Game({Key? key}) : super(key: key);
#override
State<Guess_Game> createState() => _Guess_GameState();
}
class _Guess_GameState extends State<Guess_Game> {
void initState() {
super.initState();
init_random_url = randomly_select_URL();
GuessGameFuture = API.get_pets(init_random_url);
}
void requestAgain() {
setState(() {
init_random_url = randomly_select_URL();
GuessGameFuture = API.get_pets(init_random_url);
});
}
#override
Widget build(BuildContext context) {
init_random_url = randomly_select_URL();
return Scaffold(
body: Center(
child:
FutureBuilder<List<dynamic>>(
future: GuessGameFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? pet_data = snapshot.data;
var number_of_parameters = snapshot.data!.length;
var random_pet = random.nextInt(number_of_parameters);
var category = pet_data![random_pet].category.toString();
var photoURL = pet_data![random_pet].photoUrls;
// Here is the condition that ensure pet category is in the list and has an image
if (checkCategoryInList(category, items) &&
photoURL.length != 0) {
return Random_Card(
pet_data: pet_data,
random_pet: random_pet,
dropdownvalue: dropdownvalue);
} else {
if (photoURL.length == 0) {
print(" NO PHOTO SUBMITTED FOR THIS PET");
} else {
print(category + "NOT IN CATEGORY");
}
WidgetsBinding.instance.addPostFrameCallback((_) {
requestAgain();
});
}
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return const CircularProgressIndicator();
},
)
else
const Text(
"Please select your guess",
style: TextStyle(fontSize: 17, color: Colors.indigo),
),
),
),
}
}
Add this line to build
GuessGameFuture = API.get_pets(randomly_select_URL());
and Change requestAgain function to this:
void requestAgain() {
setState(() {
GuessGameFuture = API.get_pets(randomly_select_URL());
});
}
Also you can use FutureProvider and riverpod library.
Hope it helps

shared preferences does not save radio button checkmark in Flutter

I implemented the shared preferences package in my Flutter app, with a list widget as radio button, that only save the language preference and not the checkmark.
So when i close the Language screen and come back, the language checkmark goes the the default one even if the language, saved in shared preferences is French or Italian.
This is my Language screen:
class LanguagesScreen extends StatefulWidget {
const LanguagesScreen({Key? key}) : super(key: key);
#override
State<LanguagesScreen> createState() => _LanguagesScreenState();
}
class Item {
final String prefix;
final String? helper;
const Item({required this.prefix, this.helper});
}
var items = [
Item(prefix: 'English', helper: 'English',), //value: 'English'
Item(prefix: 'Français', helper: 'French'),
Item(prefix: 'Italiano', helper: 'Italian'),
];
class _LanguagesScreenState extends State<LanguagesScreen> {
var _selectedIndex = 0;
final _userPref = UserPreferences();
var _selecLangIndex;
int index = 0;
final List<String> entries = <String>['English', 'French', 'Italian'];*/
//init shared preferences
#override
void initState() {
super .initState();
_populateField();
}
void _populateField() async {
var prefSettings = await _userPref.getPrefSettings();
setState((){
_selecLangIndex = prefSettings.language;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(...
),
body: CupertinoPageScaffold(
child: Container(
child: SingleChildScrollView(
child: CupertinoFormSection.insetGrouped(
children: [
...List.generate(items.length, (index) => GestureDetector(
onTap: () async {
setState(() => _selectedIndex = index);
if (index == 0){
await context.setLocale(Locale('en','US'));
_selecIndex = Language.English;
}
else if (index == 1){
await context.setLocale(Locale('fr','FR'));
_selecIndex = Language.French;
}
child: buildCupertinoFormRow(
items[index].prefix,
items[index].helper,
selected: _selectedIndex == index,
)
)),
TextButton(onPressed:
_saveSettings,
child: Text('save',
)
buildCupertinoFormRow(String prefix, String? helper, {bool selected = false,}) {
return CupertinoFormRow(
prefix: Text(prefix),
helper: helper != null
? Text(helper, style: Theme.of(context).textTheme.bodySmall,)
:null, child: selected ? const Icon(CupertinoIcons.check_mark,
color: Colors.blue, size: 20,) :Container(),
);
}
void _saveSettings() {
final newSettings = PrefSettings(language:_selecIndex);
_userPref.saveSettings(newSettings);
Navigator.pop(context);
}
}
this is the UserPreference:
class UserPreferences {
Future saveSettings(PrefSettings prefSettings) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setInt('language' , prefSettings.language.index );
}
Future<PrefSettings> getPrefSettings() async {
final preferences = await SharedPreferences.getInstance();
final language = Language.values[preferences.getInt('language') ?? 0 ];
return PrefSettings(language: language);
}
}
enum Language { English, French, Italian}
class PrefSettings{
final Language language;
PrefSettings (
{required this.language});
}
I'm betting that the issue is in initState. You are calling _populateField, but it doesn't complete before building because it's an async method, and you can't await for it: so the widget gets build, loading the default position for the checkmark, and only after that _populateField completes...but then it's too late to show the saved data correctly.
In my experience, if I have not already instantiated a SharedPreferences object somewhere else in the code, I use this to load it:
class _LanguagesScreenState extends State<LanguagesScreen> {
[...]
#override
Widget build(BuildContext context) {
return FutureBuilder(
//you can put any async method here, just be
//sure that you use the type it returns later when using 'snapshot.data as T'
future: await SharedPreferences.getInstance(),
builder: (context, snapshot) {
//error handling
if (!snapshot.hasData || snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
var prefs= snapshot.data as SharedPreferences;
//now you have all the preferences available without need to await for them
return Scaffold((
[...]
);
EDIT
I started writing another comment, but there are so many options here that there wasn't enough space.
First, the code I posted should go in your _LanguagesScreenState build method. The FutureBuilder I suggested should wrap anything that depends on the Future you must wait for to complete. I put it up at the root, above Scaffold, but you can move it down the widgets' tree as you need, just remember that everything that needs to read the preferences has to be inside the FutureBuilder.
Second, regarding SharedPreferences.getInstance(), there are two ways: the first is declaring it as a global variable, and loading it even in the main method where everything starts. By doing this you'll be able to reference it from anywhere in your code, just be careful to save the changes everytime is needed. The second is to load it everytime you need, but you'll end up using a FutureBuilder a lot. I don't know if any of these two options is better than the other: the first might have problems if somehow the SharedPreferences object gets lost, while the second requires quite more code to work.

How to solve Unhandled Exception: FormatException: Could not find End of Central Directory Record while downloading file in Flutter?

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.

How to show picked image with file_picker in web?

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;
});
}
});
}
});
}
}

Flutter: How to display a short text file from assets on screen of phone?

I have ckecked all the answers about reading and writing file in Flutter. None of them answers the question of how to display a text file on the screen of the phone.
All I want to do is to have a function/method to call with the filename as input, which will display a short text file from my assets directory on a new screen on the phone that I have navigated to. The file is correctly placed in assets and mentioned in the yaml file. I have seen the suggestion to use:
Future loadAsset() async {
return await rootBundle.loadString('assets/my_text.txt');
}
but I don't know how to use it and what code to use to display a file on the screen.
You do not need to add Esen Mehmet's code to a new file. Suppose you are pressing a button which opens up the text file on a new page, you just need to add the below code in the same class :
Future _future;
Future<String> loadString() async =>
await rootBundle.loadString('assets/text.txt');
#override
void initState() {
_future = loadString();
super.initState();
And the below code in the body of the scaffold:
FutureBuilder(
future: _future,
builder: (context, snapshot) =>
Text(snapshot.hasData ? '${snapshot.data}' : ' Reading...')),
I suppose that you know how to display a Text on the Screen, so I will just try to explain how I normally read files.
First you have to import:
import 'package:path_provider/path_provider.dart';
import 'dart:io';
and then, in your class, you can use this:
Future<void> readMyFile() async {
Directory directory = await getApplicationDocumentsDirectory();
var _localFilePath = (directory.path + "yourfile.txt");
if (FileSystemEntity.typeSync(_localFilePath) == FileSystemEntityType.file) {
final myFile = await _localFile(_localFilePath);
List<String> linesAsList = await myFile.readAsLinesSync();
for (var i = 0; i < linesAsList.length; i++) {
//print("Line No: " + i.toString() + "\n");
print(linesAsList[i]);
}
}
}
Future<File> _localFile(String myPath) async {
return File(myPath);
}
At the end, the content of your file is in linesAsList as a list of lines.
First this code to have a new screen and call your code:
child:
FlatButton(
onPressed: () {
Navigator.pushNamed(context, '/indled');
setState(() {
ReadFile(fileName: 'myfile.txt');
print('Button 1 got pressed');
});
},
......
It prints Button 1 got pressed on console and goes fine to new screen indled, which has the code:
class Indled extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Indledning'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.all(8.0),
child: Text('Indledning 2'),
It prints 'Indledning 2' on the screen as a test, but nothing more happens. I have your code as the following in a separate file:
class ReadFile {
ReadFile({this.fileName});
final String fileName;
Future<void> readMyFile() async {
Directory directory = await getApplicationDocumentsDirectory();
var _localFilePath = (directory.path + "myfile.txt");
if (FileSystemEntity.typeSync(_localFilePath) ==
FileSystemEntityType.file) {
final myFile = await _localFile(_localFilePath);
List<String> linesAsList = myFile.readAsLinesSync();
for (var i = 0; i < linesAsList.length; i++) {
//print("Line No: " + i.toString() + "\n");
print(linesAsList[i]);
}
}
}
Future<File> _localFile(String myPath) async {
return File(myPath);
}
}
On the line: List linesAsList = await myFile.readAsLinesSync();
I get a warning on await: Await only futures
so I took out await. But same result if await is included.
I have tried to put fileName instead of "my file.txt" in your code, but same result.