I want user to enter text in textfield. When user clicks the fab, the text is written to a file as a new line(appending). I want my app to read the contents of the file and show each line as a listtile in a listview, below the input textfield. When user enters a new text, that should appear in the listview instantaneously.
I was able to do up to writing the text to the file. But how to read the file and display its contents? Should I use streambuilder?. Below is the code I did till now:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Path Provider',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Path Provider', storage: FileStorage(),),
);
}
}
class FileStorage {
Future<String> get _localPath async {
final directory = await getTemporaryDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/file.txt');
}
void readFile() {
/* What to do? */
}
Future<Null> writeFile(String text) async {
final file = await _localFile;
IOSink sink = file.openWrite(mode: FileMode.append);
sink.add(utf8.encode('$text'));
await sink.flush();
await sink.close();
}
}
class MyHomePage extends StatefulWidget {
final FileStorage storage;
MyHomePage({Key key, this.title, this.storage}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final myController = TextEditingController();
#override
void dispose() {
// TODO: implement dispose
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: new Text('Testing'),
),
body: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
controller: myController,
decoration: new InputDecoration(
hintText: 'Enter the text',
),
),
),
// StreamBuilder(
// stream: widget.storage.readCounter().asStream(),
// )
],
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save_alt),
onPressed: () {
widget.storage.writeFile(myController.text);
},
),
);
}
}
An example of reading a file as a stream is actually shown on the File documentation, but the stream ends once you are done reading the file...I don't think it will keep sending you data if you write to it later, but try it out. If you want to observe changes to the file, try using the file.watch function, which returns a Stream of FileSystemEvent. Watch for FileSystemEvent.modify, then each time getting an event, you could call a function to read the file and redisplay all the contents.
This design may be overkill because you could just read the file once on init and keep the state of the list of strings in a state variable or state framework like Redux. Since you are controlling all the writes to the file, barring any errors while writing, your state should be what is saved in the file, so there's no point in reading the file over and over. Here's a sample class that does just that:
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class ReadFileScreen extends StatefulWidget {
#override
ReadFileScreenState createState() {
return new ReadFileScreenState();
}
}
class ReadFileScreenState extends State<ReadFileScreen> {
final myController = TextEditingController();
final storage = FileStorage();
List<String> lines = [];
#override
void initState() {
super.initState();
_loadFile();
}
//can not make initState() async, so calling this function asynchronously
_loadFile() async {
final String readLines = await storage.readFileAsString();
debugPrint("readLines: $readLines");
setState(() {
lines = readLines.split("\\n"); //Escape the new line
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Testing'),
),
body: new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: new TextField(
controller: myController,
decoration: new InputDecoration(
hintText: 'Enter the text',
),
),
),
new Expanded(
child: new ListView.builder(
itemCount: lines.length,
itemBuilder: (context, index) {
return new Text(lines[index]); //Replace with ListTile here
}),
),
],
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save_alt),
onPressed: () {
final String enteredText = myController.text;
storage.writeFile(enteredText);
myController.clear();
setState(() {
lines.add(enteredText);
});
},
),
);
}
}
class FileStorage {
Future<String> get _localPath async {
final directory = await getTemporaryDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/file.txt');
}
Future<String> readFileAsString() async {
String contents = "";
final file = await _localFile;
if (file.existsSync()) { //Must check or error is thrown
debugPrint("File exists");
contents = await file.readAsString();
}
return contents;
}
Future<Null> writeFile(String text) async {
final file = await _localFile;
IOSink sink = file.openWrite(mode: FileMode.APPEND);
sink.add(utf8.encode('$text\n')); //Use newline as the delimiter
await sink.flush();
await sink.close();
}
}
Related
I tried share package and it works but I want to add a Widget that only appears on the shared image, not on the app. So please help me this is the code so far:
import 'dart:ui';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:share/share.dart';
void main() {
runApp(MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
final _contentKey = GlobalKey();
final bool forSharing;
HomePage({
Key key,
this.forSharing = false,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.share),
onPressed: () => onTakeScreenShotAndSharing(
'Image',
_contentKey.currentContext,
),
),
],
),
body: RepaintBoundary(
key: _contentKey,
child: Column(
children: [
if (forSharing) Text('Text appear only for Sharing'), // This is the widget that I want it appear only on the shared image
Text('Content'),
],
),
),
);
}
void onTakeScreenShotAndSharing(String fileName, BuildContext context) async {
try {
RenderRepaintBoundary boundary = context.findRenderObject();
final image = await boundary?.toImage();
final bytes = await image?.toByteData(format: ImageByteFormat.png);
if (bytes == null || bytes.buffer == null) return;
final dir = (await getApplicationDocumentsDirectory()).path;
final file = File('$dir/$fileName${DateTime.now().toString()}.png');
final newFile = await file.writeAsBytes(bytes.buffer.asUint8List());
await Share.shareFiles([newFile.path]);
} catch (error) {
print(error);
}
}
}
I am very new to flutter and coding. This may be an easy fix but I cannot find a solution anywhere or reconfigure the code to display the name of the file I am picking.
I am trying to create a web page that can accept document submissions. However, I cannot get the code to display the name of the file picked.
The print call is working, however, the fileNameSelected does not update.
Thank you for any help!!
import 'dart:html';
import 'dart:io' as io; //type cast
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:path/path.dart';
import './appbar/appbar.dart';
class UploadFPage extends StatefulWidget {
const UploadFPage({Key? key}) : super(key: key);
#override
State<UploadFPage> createState() => _UploadFPageState();
}
class _UploadFPageState extends State<UploadFPage> {
UploadTask? task;
File? file;
String fileNameSelected = 'No File Selected';
#override
Widget build(BuildContext context) {
final fileName = fileNameSelected;
return Scaffold(
appBar: AppBar(
title: const Text('AppBar'),
),
body: ListView(
children: [
Container(
height: 300,
color: Colors.amber,
child: Row(
children: [
ElevatedButton.icon(
icon: const Icon(Icons.attach_file, size: 28),
onPressed: selectFile,
label: Text("Attach Invoice"),
),
Text(fileName),
],
),
),
],
),
);
}
Future selectFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowMultiple: false,
allowedExtensions: ['pdf']);
if (result == null) {
return;
} else {
print(result.files.single.name);
//final name = result.files.single.name;
//final path = result.files.single.path!;
//final file = result.files.single.bytes!;
setState(() {
String fileNameSelected = basename(result.files.single.name);
//file = File(size, path);
});
}
}
}
Change:
setState(() {
String fileNameSelected = basename(result.files.single.name);
});
to
setState(() {
fileNameSelected = basename(result.files.single.name);
});
By adding var, final or String in front you are declaring a new variable (which happens to have the same name as the one you declared above). You assign your picked file to that - and it promptly goes out of scope.
Whenever I click Run Query button I get the expected result in the terminal but I have to click it once again to be able to display the result in the UI!
I guess there is something wrong in setState usage. May you please help me to find out what's wrong?
PS. I tried to use both suggested cases of setState mentioned in this post but I always get the same result.
import 'package:flutter/material.dart';
import 'package:mysql1/mysql1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MySQL Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'MySQL Test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? _barcode;
late bool visible;
#override
var Connection;
String? res;
void initsql() async {
try {
var settings = ConnectionSettings(
host: '127.0.0.1',
port: 3306,
user: 'root',
db: 'testo',
password: '1234',
timeout: Duration(minutes: 2));
Connection = await MySqlConnection.connect(settings);
print("Connected");
} catch (e) {
print(e);
}
}
void getResult() async {
var results = await Connection.query('SELECT id, Name FROM testo.mytable');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
}
void initState() {
initsql();
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
res == null ? 'Disconnected!' : '$res',
style: Theme.of(context).textTheme.headline5,
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: [
ElevatedButton(
onPressed: () => setState(() {
//---> Needs Double click!!
getResult();
}),
child: Center(child: Text('Run Query'))),
],
),
)
],
),
),
);
}
}
First of all your getResult method is asynchronous, so the return type is not void but Future<void>.
Future<void> getResult() async {
var results = await Connection.query('SELECT id, Name FROM testo.mytable');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
}
The reason why you UI doesn't update the widget on your first tap is, that your getResult is asynchronous. In your button you call setState, which triggers a rebuild of your widget, but since the method is asynchronous, it cannot provide a result immediately when it is started. This means you call setState, it updates your UI, then your method retrieves the new value of getResult, but the UI got updated before that. To fix this you have to wait for your method until it has finished and then call setState to update your UI.
ElevatedButton(
onPressed: () async {
await getResult();
setState((){});
} ,
child: Center(
child: Text('Run Query'),
),
);
#qouci explained well instead converting you fuction future.you can also use like this .add setstate after asyn query execution.
Widget
ElevatedButton(
onPressed: () => getResult(),
child: Center(child: Text('Run Query')))
QueryExecution
void getResult() async {
var results = await Connection.query('SELECT * FROM test.sys_config;');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
// ------------------------------------here
setState(() {
res;
//---> Needs Double click!!
});
}
I have successfully implemented the flutter_secure_storage in my flutter project, when the user writes his email and password, it get's stored, but here is the thing I don't understand. How should I setup screen routes depending if the user has already logged in or not. If it is the same user, so the username and pass are stored in the secure_storage, I want him to go directly to HomeScreen(), but if there is a new user that needs to log in, so there is no data in the secure_storage, then I want him sent to LoginScreen(). I have done this so far:
import 'dart:async';
import 'package:flutter/material.dart';
import 'login_screen.dart';
import 'home_screen.dart';
import 'components/alarm_buttons.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class WelcomeScreen extends StatefulWidget {
static const String id = 'welcome_screen';
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
void readData() async {
final storage = FlutterSecureStorage();
String myPassword = await storage.read(key: "p");
String myEmail = await storage.read(key: "e");
print(myEmail);
print(myPassword);
}
#override
void initState() {
final storage = FlutterSecureStorage();
Timer(
Duration(seconds: 2),
() => Navigator.pushNamed(
context,
storage == null ? LoginScreen.id : HomePage.id,
));
readData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
AlarmButtons(
buttonColour: Colors.grey,
buttonText: 'Log In',
buttonTextColour: Colors.white,
onButtonPress: () {
Navigator.pushNamed(context, LoginScreen.id);
},
),
AlarmButtons(
buttonColour: Colors.white,
buttonText: 'Sign up',
buttonTextColour: Colors.grey,
onButtonPress: () {
Navigator.pushNamed(context, SignUpScreen.id);
},
),
],
),
),
);
}
}
Now the problem starts when I want to return to the Welcome Screen (the starting page of my app shown above), naturally it triggers the initState again and I get back to the HomePage() again. How can I dispose of that, only triggering that initState when the app starts, so after automatic login I can return to the Welcome Screen without triggering it?
Thanks in advance!
You should initial start something like a SplashScreen (or WelcomeScreen in your case). There you can decide to which screen you want to navigate based on the saved data. Example:
class SplashScreen extends StatefulWidget {
const SplashScreen({Key key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
_startApp();
super.initState();
}
Future<void> _startApp() async {
final storage = FlutterSecureStorage();
String myEmail = await storage.read(key: "e");
if (myEmail == null) {
// TODO Navigate to Login Screen
} else {
// TODO Navigate to Home Screen
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Splashscreen"),
),
);
}
}
I am making an app which loads the CSV and show the table on the screen but the load function is being called infinitely in the build state can anyone know how to fix it I wanted to call only once but my code called it many times.
Here is the console screenshot:
Here is the code:
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
List<List<dynamic>> data = [];
loadAsset() async {
final myData = await rootBundle.loadString("asset/dreamss.csv");
List<List<dynamic>> csvTable = CsvToListConverter().convert(myData);
return csvTable;
}
void load() async{
var newdata = await loadAsset();
setState(() {
data = newdata;
});
print("am i still being called called ");
}
#override
Widget build(BuildContext context) {
load();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Apps"),),
//floatingActionButton: FloatingActionButton( onPressed: load,child: Icon(Icons.refresh),),
body: ListView(
children: <Widget>[
Container(margin: EdgeInsets.only(top: 20.0),),
Table(
border: TableBorder.all(width: 1.0,color: Colors.black),
children: data.map((item){
return TableRow(
children: item.map((row){
return Text(row.toString(),style: TextStyle(fontSize: 20.0,fontWeight: FontWeight.w900),);
}).toList(),
);
}).toList(),
),
]),
));
}
}
Here is the solution.
#override
void initState() {
super.initState();
load(); // use it here
}
#override
Widget build(BuildContext context) {
return MaterialApp(...); // no need to call initState() here
}