0

I want to create a widget that lists all of the sub directories in a given directory. I have elected to use StreamBuilder to achieve this, here is my app:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

void main() {
  runApp(ArmyMakerApp());
}

class ArmyMakerApp extends StatelessWidget {
  const ArmyMakerApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => ArmyMakerAppState(),
      child: MaterialApp(
        title: 'Army Maker',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        ),
        home: ArmiesPage(),
      ),
    );
  }
}

class ArmyMakerAppState extends ChangeNotifier {}

class ArmiesPage extends StatefulWidget {
  @override
  State<ArmiesPage> createState() => _ArmiesPageState();
}

class _ArmiesPageState extends State<ArmiesPage> {
  Stream<FileSystemEntity>? _repositories;

  List<FileSystemEntity> _repositoriesList = [];

  _ArmiesPageState() {
    getRepositoriesDirectory().then((repositoriesDirectory) => setState(() {
          _repositories = repositoriesDirectory.list(recursive: false);
        }));
  }

  @override
  Widget build(BuildContext context) {
    final Stream<FileSystemEntity>? repositories = _repositories;

    return StreamBuilder(
        stream: repositories,
        builder:
            (BuildContext context, AsyncSnapshot<FileSystemEntity> snapshot) {

          if (snapshot.hasData) {
            _repositoriesList.add(snapshot.data!);
          }

          return Scaffold(
            body: ListView.builder(
                itemCount: _repositoriesList.length,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text(_repositoriesList[index].path));
                }),
          );
        });
  }
}

Future<Directory> getDocumentsDirectory() async {
  final rootDocumentsDirectory = await getApplicationDocumentsDirectory();

  final documentsDirectory =
      Directory(path.join(rootDocumentsDirectory.path, "army_maker"));

  return documentsDirectory;
}

Future<Directory> getRepositoriesDirectory() async {
  final directory = await getDocumentsDirectory();

  return Directory(path.join(directory.path, 'repositories'));
}

In theory, repositoriesList should be extended to contain all the files in ~/Documents/army_maker/repositories. But not only does this not happen but the behavior of my widget appears to be completely arbitrary. For testing purposes I have created a single subdirectory ~/Documents/army_maker/repositories/foo. Now when, when I start my app, _ArmiesPageState.build is called four times in total, two of which add foo to _repositoriesList. I would expect build to only be called once. Why is this happening and how can I fix it?

Peter
  • 2,919
  • 1
  • 16
  • 35
  • You should not be using async on something that's nearby like a directory listing. It'd be a whole lot simpler to do this in a synchronous (but cached) manner. – Randal Schwartz Jun 04 '23 at 19:59
  • @RandalSchwartz That might be true, but in a second step I would also like to dynamically update the ListView when the directory content changes which would require the async `watch()` method. – Peter Jun 04 '23 at 20:05
  • @Peter I would suggest to use create StreamController and add data using sink and receive using stream from the controller and add the StreamController on initState() method and avoid calling setState() which is causing this issue. – Arbaz Shaikh Jun 09 '23 at 07:12

0 Answers0