3

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.

Preview

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);
        },
      ),
    );
  }
}
Prasanth Kanna
  • 1,961
  • 4
  • 18
  • 25

1 Answers1

4

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();
  }
}
Mayank
  • 1,595
  • 1
  • 11
  • 26
tuan vu
  • 223
  • 2
  • 6
  • 4
    Upvoted for the additional info on file.watch. Big difference between just reading a file and watching the file as a stream. – Rap Aug 13 '19 at 15:13