How to make BLoC instance persistent? - flutter

I'm developing an app using movies database API. I want to read a list of the movies and then choose one and display the details to user.
MovieDetailsBloc code:
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_details.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:stream_transform/stream_transform.dart';
part 'movie_details_event.dart';
part 'movie_details_state.dart';
const throttleDuration = Duration(milliseconds: 100);
EventTransformer<E> throttleDroppable<E>(Duration duration) {
return (events, mapper) {
return droppable<E>().call(events.throttle(duration), mapper);
};
}
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieListPage code:
import 'package:flutter/material.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movies/bloc/movies_bloc.dart';
import 'package:flutter_recruitment_task/movies/models/movie.dart';
import 'package:flutter_recruitment_task/pages/movie_details/movie_details_page.dart';
import 'package:flutter_recruitment_task/pages/movie_list/movie_card.dart';
import 'package:flutter_recruitment_task/pages/movie_list/search_box.dart';
import 'package:flutter_recruitment_task/services/api_service.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MovieListPage extends StatefulWidget {
#override
_MovieListPage createState() => _MovieListPage();
}
class _MovieListPage extends State<MovieListPage> {
final ApiService apiService = ApiService();
late final MoviesBloc moviesBloc;
late final MovieDetailsBloc movieDetailsBloc;
void _onSearchBoxSubmitted(String text) {
moviesBloc.add(MoviesFetched(query: text));
}
#override
void initState() {
moviesBloc = MoviesBloc(apiService: apiService);
movieDetailsBloc = MovieDetailsBloc(apiService: apiService);
super.initState();
}
#override
void dispose() {
movieDetailsBloc.close();
moviesBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => movieDetailsBloc,
child: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.movie_creation_outlined),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => MovieDetailsPage(
movieDetailsBloc: movieDetailsBloc,
)
)
);
},
),
],
title: Text('Movie Browser'),
),
body: BlocProvider(
create: (_) => moviesBloc,
child: Column(
children: <Widget>[
SearchBox(onSubmitted: _onSearchBoxSubmitted),
Expanded(child: _buildContent()),
],
),
),
),
);
Widget _buildContent() => BlocBuilder<MoviesBloc, MoviesState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
switch (state.status) {
case MoviesStatus.failure:
return const Center(child: Text('failed to fetch movies'));
case MoviesStatus.success:
if (state.movies.isEmpty) {
return const Center(child: Text('no movies'));
}
return _buildMoviesList(state.movies);
case MoviesStatus.initial:
return const Center(child: CircularProgressIndicator());
}
});
Widget _buildMoviesList(List<Movie> movies) => ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
return MovieCard(
title: movies[index].title,
rating: '${(movies[index].voteAverage * 10).toInt()}%',
color: state.movie.title == movies[index].title ?
Colors.amberAccent : Colors.white,
onTap: () {
movieDetailsBloc.add(MovieSelected(query: movies[index].id.toString()));
},
);
}
),
itemCount: movies.length,
);
}
MovieDetailsPage code:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/bloc/movie_details_bloc.dart';
import 'package:flutter_recruitment_task/movie_details/models/movie_detail_placeholder.dart';
class MovieDetailsPage extends StatefulWidget {
final MovieDetailsBloc? movieDetailsBloc;
const MovieDetailsPage({super.key, this.movieDetailsBloc});
#override
_MovieDetailsPageState createState() => _MovieDetailsPageState();
}
class _MovieDetailsPageState extends State<MovieDetailsPage> {
var _details = [];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => BlocProvider(
create: (_) => widget.movieDetailsBloc!,
child: BlocBuilder<MovieDetailsBloc, MovieDetailsState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
_details = [
MovieDetailPlaceholder(title: 'Budget', content: '\$ ${state.movie.budget}'),
MovieDetailPlaceholder(title: 'Revenue', content: '\$ ${state.movie.revenue}'),
MovieDetailPlaceholder(title: 'Should I watch it today?', content: widget.movieDetailsBloc!.shouldIWatchIt()),
];
return Scaffold(
appBar: AppBar(
title: Text(state.movie.title),
),
body: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1.0,
color: Colors.grey.shade300,
),
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
_details[index].title,
style: Theme.of(context).textTheme.headline5,
),
SizedBox(height: 8.0),
Text(
_details[index].content,
style: Theme.of(context).textTheme.subtitle1,
),
],
),
),
itemCount: _details.length,
),
);
}
),
);
}
I'm passing an instance of BLoC from MovieListPage to MovieDetailsPage because it holds the details of the movie to display. It works fine for one time, however when I go back to the MovieListPage I'm not able to choose a new movie to display the details. How I can make an instance of BLoC persist through navigation?

Create your bloc's intance after your bloc ends like
class MovieDetailsBloc extends Bloc<MovieDetailsEvent, MovieDetailsState> {
MovieDetailsBloc({required this.apiService}) : super(MovieDetailsState()) {
on<MovieSelected>(
_onMovieSelected,
transformer: throttleDroppable(throttleDuration),
);
}
final ApiService apiService;
Future<void> _onMovieSelected(MovieSelected event, Emitter<MovieDetailsState> emit) async {
try{
final movie = await _fetchMovieDetails(event.query);
return emit(state.copyWith(
status: MovieDetailsStatus.success,
movie: movie,
));
} catch (_) {
emit(state.copyWith(status: MovieDetailsStatus.failure));
}
}
Future<MovieDetails> _fetchMovieDetails(String id) async {
final movie = apiService.fetchMovieDetails(id);
return movie;
}
String shouldIWatchIt() {
final movie = state.movie;
if(movie.revenue - movie.budget > 1000000 && DateTime.now().weekday == DateTime.sunday){
return 'Yes';
} else {
return 'No';
}
}
}
MovieDetailsBloc movieDetailsBloc = MovieDetailsBloc();
and you can access it anywhere in your code with movieDetailsBloc.add or whatever you want.

Related

Widget is not rebuilding when notifyListeners is called

i am trying to create a login page so that when i am logged-in, the Navigationrail Widget lets me access all its destinations. When logged off i can only access two pages.
I am using Provider in login.dart to triger a widget rebuild in main.dart .
here is the code.
login.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:httptest/depand/XmlLogin.dart';
import 'package:httptest/main.dart';
void login(String userName, String passWord) async {
Response response;
Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
var token = getToken.Transcribe(response.data);
LoginProvider obj = LoginProvider();
obj.providestate(true);
print(token);
print("logged in");
handler.next(response);
},
));
try {
//Http Post method
} catch (e) {
print(e);
}
}
class LoginProvider extends ChangeNotifier {
bool loginstate = false;
void providestate(bool val) {
loginstate = val;
print("loginstate changed to $loginstate");
notifyListeners();
}
}
main.dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
List<Widget> pages = [Page0(), Page1(), Page2(), Placeholder()];
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: ChangeNotifierProvider<LoginProvider>(
create: (context) => LoginProvider(),
child: Builder(
builder: (context) {
return Consumer<LoginProvider>(
builder: (context, provider, child) {
return NavigationRail(
extended: constraints.maxWidth >= 600,
minExtendedWidth: 200,
destinations: [
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination()
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
if (provider.loginstate) {
setState(() {
selectedIndex = value;
});
print("On");
} else {
if (value == 0 || value == 3) {
setState(() {
selectedIndex = value;
});
print("OFF");
}
}
},
);
});
},
),
)),
Expanded(
child: Scaffold(
body: IndexedStack(
index: selectedIndex,
children: pages,
),
),
),
],
),
);
});
}
}
the login goes through but i Still cant access pages 1 and 2.
it prints out 'loginstate changed to True' from login.dart.
To ensure access place a provider in a widget tree something like this:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginProvider()),
...
],
child: const MyApp(),
),
);
}
Then in child widgets access it using either Consumer to listen changes or Provider.of when just calling a method.
Consumer<LoginProvider>(
builder: (context, provider, child) {
...
},
)
Provider.of<LoginProvider>(context, listen: false).login(userName, passWord);
See Simple app state management for the full example.

make a pagination with flutter and bloc

ServiceRepo
import 'package:service_youtube/data/service_model.dart';
import 'package:http/http.dart';
class ServicesRepo {
final _url = "http://osamastartup.osamamy-class.com/api/services";
Future<List<Datum>> getServices(int page) async {
final response = await get(Uri.parse("$_url?page=$page"));
final services = serviceFromJson(response.body);
return services.data;
}
}
the _url and everything is correct and return data without any problem
ServiceBloc:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_event.dart';
import 'package:service_youtube/blocs/service_bloc/service_state.dart';
import '../../data/service_repo.dart';
class ServiceBloc extends Bloc<ServiceEvent, ServiceState> {
final ServicesRepo repo;
int page = 1;
bool isFetching = false;
ServiceBloc(this.repo) : super(ServiceLoadingState()) {
on<LoadServicesEvent>((event, emit) async {
emit(ServiceLoadingState());
try {
final services = await repo.getServices(page);
emit(ServiceLoadedState(services: services));
page++;
} catch(e) {
emit(ServiceErrorState(msg: e.toString()));
}
});
}
}
surly i have all states and events , and there are no problem with them,
ServiceView
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:service_youtube/blocs/internet_bloc/internet_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_bloc.dart';
import 'package:service_youtube/blocs/service_bloc/service_event.dart';
import 'package:service_youtube/data/service_model.dart';
import '../blocs/internet_bloc/internet_state.dart';
import '../blocs/service_bloc/service_state.dart';
class ServiceView extends StatelessWidget {
ServiceView({Key? key}) : super(key: key);
final List<Datum> _services = [];
final ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Services App"),),
body: Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(5),
child: Center(
child: BlocListener<ConnectedBloc, ConnectedState>(
listener: (context, state) {
if (state is ConnectedSucessState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Internet Connected')));
} else if (state is ConnectedFailureState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Internet Lost')));
}
},
child: BlocBuilder<ServiceBloc, ServiceState>(
builder: (context, state) {
if(state is ServiceLoadingState && _services.isEmpty) {
return const CircularProgressIndicator();
} else if (state is ServiceLoadedState) {
_services.addAll(state.services);
context.read<ServiceBloc>().isFetching = false;
return ListView.separated(
controller: _scrollController
..addListener(() {
if (_scrollController.offset ==
_scrollController.position.maxScrollExtent &&
!context.read<ServiceBloc>().isFetching) {
context.read<ServiceBloc>()
..isFetching = true
..add(LoadServicesEvent());
}
}),
itemCount: _services.length,
separatorBuilder: (context, index) => const SizedBox(height: 50),
itemBuilder: (context, index) {
return ListTile(
title: Text(_services[index].name,),
subtitle: Text(_services[index].info,),
leading: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(_services[index].image),
),
);
},
);
} else if (state is ServiceErrorState && _services.isEmpty) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () {
context.read<ServiceBloc>()
..isFetching = true
..add(LoadServicesEvent());
},
icon: const Icon(Icons.refresh),
),
const SizedBox(height: 15),
Text(state.msg, textAlign: TextAlign.center),
],
);
} else {
return Container();
}
},
),
),
),
),
);
}
}
the data is appear without any problem , but after i scroll show me just a blank screen, without any error message? where is the problem?

The search bar does not return any results

I am trying to add a search function in my flutter app, the search bar is showing and there's not errors but its not working and it doesn't return any results.
the data list is from an API that I already called using the rest API
// ignore_for_file: use_key_in_widget_constructors, avoid_print, avoid_unnecessary_containers, curly_braces_in_flow_control_structures, prefer_const_constructors, non_constant_identifier_names, unnecessary_new, avoid_function_literals_in_foreach_calls, unused_import, avoid_types_as_parameter_names, unused_label
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:myapp2/Service_Request/SR.dart';
import 'package:myapp2/main.dart';
import 'package:myapp2/Service_Request/second.dart';
import '../Classes/demandes.dart';
import 'SR_details.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DataFromAPI(),
);
}
}
class DataFromAPI extends StatefulWidget {
#override
_DataFromAPIState createState() => _DataFromAPIState();
}
List<Attributes> _MyAllData = [];
var _srAttributes = [];
class _DataFromAPIState extends State<DataFromAPI> {
#override
void initState() {
loadData().then((value) {
setState(() {
_srAttributes.addAll(value);
});
});
super.initState();
}
Future<List<Sr>> loadData() async {
try {
var response = await http.get(Uri.parse(
'http://192.168.1.30:9080/maxrest/rest/mbo/sr/?_lid=azizl&_lpwd=max12345m&_format=json'));
if (response.statusCode == 200) {
final jsonBody = json.decode(response.body);
Demandes data = Demandes.fromJson(jsonBody);
final srAttributes = data.srMboSet.sr;
return srAttributes;
}
} catch (e) {
throw Exception(e.toString());
}
throw Exception("");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text('Liste des Demandes'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => SR()))),
),
body: FutureBuilder<List<Sr>?>(
future: loadData(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
return new ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: ((_, index) {
return index == 0
? _searchbar()
: new ListTile(
title: new Card(
margin: new EdgeInsets.symmetric(
vertical: 2.0, horizontal: 8.0),
elevation: 10,
child: new ListTile(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: new EdgeInsets.all(2.0)),
new Text(
'Ticket ID : ${snapshot.data![index].attributes.ticketid.content}'),
new Text(
'status : ${snapshot.data![index].attributes.status.content}'),
new Text(
'description : ${snapshot.data![index].attributes.description?.content}'),
new Text(
'Reported by : ${snapshot.data![index].attributes.reportedby.content}'),
new Text(
'Reoprt date : ${snapshot.data![index].attributes.statusdate.content}'),
],
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
),
),
onTap: () {
Navigator.of(context)
.push(
new MaterialPageRoute(
builder: (BuildContext context) =>
new SrDetailsScreen(
sr: snapshot.data![index]),
),
)
.then((data) {});
});
}),
);
}
},
),
),
);
}
_searchbar() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(hintText: "Search ..."),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_srAttributes = _MyAllData.where((srAttributes) {
var idticket = srAttributes.description!.content.toLowerCase();
return idticket.contains(text);
}).toList();
});
},
),
);
}
}
FutureBuilder loads values of current future. You are assigning a function result to FutureBuilder so its value always changes dynamically.
Create variable to keep Future's value.
Future<List<Sr>>? dataToLoad;
Whenever you want to load data from server ( for example, on text changed ):
setState((){
dataToLoad = loadData();
});
And use it in FutureBuilder:
FutureBuilder<List<Sr>?>(
future: dataToLoad,

Flutter Unable to display info from asynchronous call on first page of app

UPDATE
I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:
start of file_listing.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits =
await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})
],
));
});
// () => newList;
return newList;
// throw ("f");
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: fileListMappedAsWidget(),
// future: testRetrieve,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Widget> children;
print(snapshot.connectionState);
if (snapshot.hasData) {
List<Widget> childStuff = [];
for (int i = 0; i < snapshot.data!.length; i++) {
childStuff.add(snapshot.data![i]);
}
children = childStuff;
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(Icons.error_outline, color: Colors.red, size: 60),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'))
];
} else {
children = const <Widget>[
SizedBox(
width: 60, height: 60, child: CircularProgressIndicator()),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting Result'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
}),
);
}
}
end of file_listing.dart
ORIGINAL QUESTION
I'm trying to create a page for a Flutter app. It will be the first page that the user sees when they launch the app.
The page will display a list of files, cost estimates that the user has saved previously and a button for them to create a new "estimate" which can then also be saved to a file.
The files are being saved on the device, and I'm using the path_provider and path packages to access them. Reading from and writing to the device file system are asynchronous operations.
I cannot get the list of files to appear automatically when the page loads and would like to get advice on how to do this. The results of the async function are futures of data types and I cannot get them to be just data types.
Here's what I have in terms of code:
(1) main.dart
It specifies the home page of the material app to be StartPage().
start of main.dart
import 'package:flutter/material.dart';
import 'constants/text_constants.dart';
import 'pages/start_page.dart';
void main() => runApp(PythongaVisitCostCalculator());
class PythongaVisitCostCalculator extends StatelessWidget {
const PythongaVisitCostCalculator({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: kAppTitle1,
theme: ThemeData(
primarySwatch: Colors.lightBlue,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
),
),
),
),
home: StartPage(title: kYourTripsDescr));
}
}
end of main.dart
(2) start_page.dart
This is the home page (stateful class) for the application, as specified in main.dart. One of the items that appears is a list of files saved by the user using the FileListing() widget, which is created in the file_listing.dart file, which follows.
start of start_page.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pythonga_expense_estimator/components/round_icon_button.dart';
import 'package:pythonga_expense_estimator/components/file_listing.dart';
import 'package:pythonga_expense_estimator/pages/input_page.dart';
import '../constants/text_constants.dart';
class StartPage extends StatefulWidget {
const StartPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
Map fileList = {};
#override
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(kAppTitle2),
centerTitle: true,
automaticallyImplyLeading: false,
),
body: SafeArea(
child: Column(children: [
Text(kYourEstimatesDescr),
FileListing(),
Row(
children: [
Text(kEstimateTripDescr),
RoundIconButton(
icon: FontAwesomeIcons.plus,
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return InputPage(title: 'New Estimate');
}));
})
],
),
]),
),
);
}
}
end of start_page.dart
(3) file_listing.dart
This stateful class returns a widget ,FileListing(), that is used in the StartPage() widget.
start of file_listing.dart
import 'package:flutter/material.dart';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits = await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})],
));
});
}
#override
Widget build(BuildContext context) {
// if (fileList.isEmpty) {
if (fileListMappedAsWidget().isEmpty) {
return Container(
child: Row(children: const [
Center(child: Text("No Estimates on File")),
]));
}
return Column(
//children: fileList,
children: fileListMappedAsWidget(),
);
}
}
end of file_listing.dart
Dart Analysis gives me an error on the line:
children: fileListMappedAsWidget(). The error is:
The argument type 'Future<List>' can't be assigned to the parameter type 'List'.
and an error on the line if (fileListMappedAsWidget().isEmpty) {
The error is:
The getter 'isEmpty' isn't defined for the type 'Future<List>
I was expecting to that the return of fileListMappedAsWidget would be a List rather than a Future<List> once the asynchronous method completed and returned its response.
My question is "How can I transform that Future<List> to List so that my page will list the files saved by the user?
(4) directory_services.dart
Here's the code in directory_services.dart that reads the contents of the application data folder and returns a map of {File, filename text}. This code appears to be working correctly.
start of directory_services.dart
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart' as pathprvdrpkg;
import 'package:path/path.dart' as pathpkg;
import '../constants/text_constants.dart';
class DirectoryHelper {
static Future<Map<io.FileSystemEntity, String>> listOfFiles() async {
List<io.FileSystemEntity> fileSystemEntityList =
io.Directory(await localPath()).listSync();
Map<io.FileSystemEntity, String> fileMap = {};
for (int i = 0; i < fileSystemEntityList.length; i++) {
if (pathpkg.extension(fileSystemEntityList[i].path) ==
kEstimateFileExtension) {
fileMap[fileSystemEntityList[i]] = fileSystemEntityList[i].path;
}
}
return fileMap;
}
static localPath() async {
// finds the correct local path using path_provider package
try {
final directory = await pathprvdrpkg.getApplicationDocumentsDirectory();
// print("directory.path in DirectoryPath.localPath");
return directory.path;
} catch (e) {
return Exception('Error: $e');
}
}
}
end of directory_services.dart
Thanks for your help and advice!
I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:
start of file_listing.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits =
await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})
],
));
});
// () => newList;
return newList;
// throw ("f");
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: fileListMappedAsWidget(),
// future: testRetrieve,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Widget> children;
print(snapshot.connectionState);
if (snapshot.hasData) {
List<Widget> childStuff = [];
for (int i = 0; i < snapshot.data!.length; i++) {
childStuff.add(snapshot.data![i]);
}
children = childStuff;
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(Icons.error_outline, color: Colors.red, size: 60),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'))
];
} else {
children = const <Widget>[
SizedBox(
width: 60, height: 60, child: CircularProgressIndicator()),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting Result'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
}),
);
}
}
end of file_listing.dart

Not able to display initial data from server using provider

I am trying to display some initial data that gets pulled from a server in my app, I get the data but I am not able to display it. Here is my code please help
Class where data has to be displayed
import 'package:deep_pocket/models/data_feed.dart';
import 'package:deep_pocket/models/mock_data.dart';
import 'package:deep_pocket/widgets/menu_buttons.dart';
import 'package:deep_pocket/widgets/post_widget.dart';
import 'package:deep_pocket/screens/user_input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
class feedScreen extends StatefulWidget {
static const route = '/feed-screen';
#override
State<feedScreen> createState() => _feedScreenState();
}
class _feedScreenState extends State<feedScreen> {
int filter = 0;
var _intstate = true;
void updateFilter(tx, context) {
setState(() {
filter = tx;
});
Navigator.of(context).pop();
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void didChangeDependencies() {
if (_intstate) {
Provider.of<mockData>(context).fetchandAddPost();
}
_intstate = false;
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
void filterSheet(ctx) {
showModalBottomSheet(
context: ctx,
builder: (ctx) => Container(
height: 300,
child: SingleChildScrollView(
child: Container(
height: 280,
child: ListView.builder(
itemCount: Tag.length,
itemBuilder: (ctx, i) => TextButton(
onPressed: () {
return updateFilter(i, context);
},
child: Text(Tag[i]),
)),
)),
));
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<mockData>(
create: (context) => mockData(),
builder: (context, child) {
var posts = context.select((mockData m) => m.items);
print(posts.length);
if (filter != 0) {
posts = posts.where((i) => i.tag == filter).toList();
}
return Scaffold(
// drawer: Drawer(
// // Populate the Drawer in the next step.
// ),
appBar: AppBar(
title: const Text("Home"),
actions: [
TextButton(
onPressed: () => {filterSheet(context)},
child: const Text(
"Filters",
style: TextStyle(color: Colors.white),
))
],
),
body: SingleChildScrollView(
child: Column(
children: [
menuButtons(),
Container(
padding: const EdgeInsets.all(8),
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: posts.length,
itemBuilder: (ctx, i) => postWidget(post: posts[i])),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
//Waiting for result
var newData =
await Navigator.pushNamed(context, userInput.route);
if (newData != null) {
context.read<mockData>().addPost(newData as dataFeed);
}
},
child: const Icon(Icons.add)),
);
});
}
}
Class where I have my Provider and fetch setup
import 'package:deep_pocket/models/data_feed.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class mockData with ChangeNotifier {
List<dataFeed> _data = [
// dataFeed(
// id: DateTime.now().toString(),
// imgsrc: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
// name: "Priyam Srivastava",
// title: "How to change room ?",
// tag: 1,
// text:
// "I would like to know the process of changing my room cause I have not been able to study, and my roomate always plays music and drinks too much then shouts all night, please tell me how",
// ),
// dataFeed(
// id: DateTime.now().toString(),
// imgsrc: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
// title: "Anyone intresed in playing BGMI?",
// name: "Part Agarwal",
// tag: 2,
// text:
// "So I have been looing for a squad for a long time and now i have finally decided that I am gonna buckle up and ask you all to join me",
// ),
// dataFeed(
// id: DateTime.now().toString(),
// imgsrc: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
// title: "How to solve this question in O(n) complexity",
// name: "Preet Singh",
// tag: 3,
// text:
// "So I have been looing for a squad for a long time and now i have finally decided that I am gonna buckle up and ask you all to join me",
// ),
];
List<dataFeed> get items {
return [..._data];
}
Future<void> fetchandAddPost() async {
var url = link;
try {
print("getting your data");
final response = await http.get(url);
final extractedData = json.decode(response.body) as Map<String, dynamic>;
final List<dataFeed> loadedPosts = [];
extractedData.forEach((key, value) {
loadedPosts.add(dataFeed(
id: key,
imgsrc: value['imgsrc'],
name: value['name'],
title: value['title'],
text: value['text'],
date: value['date']));
});
print(loadedPosts.length);
_data = loadedPosts;
print(_data.length);
print("got your data");
notifyListeners();
} catch (e) {
print(e);
// TODO
}
}
Future<void> addPost(dataFeed newpost) async {
var url = link;
try {
final response = await http.post(
url as Uri,
body: json.encode({
'imgsrc': newpost.imgsrc,
'name': newpost.name,
'title': newpost.title,
'text': newpost.text,
'tag': newpost.tag,
'date': newpost.date,
}),
);
final newPost = dataFeed(
id: json.decode(response.body)['name'],
imgsrc: newpost.imgsrc,
name: newpost.name,
title: newpost.title,
text: newpost.text,
tag: newpost.tag,
date: newpost.date);
_data.insert(0, newPost);
notifyListeners();
} catch (e) {
print(e);
// TODO
}
}
}
I am getting data from server but it isn't being displayed, if I add new data it gets displayed.
This is the basic use age for using Provider, fetch data from network, updating ListView and pagination.
class ExampleWidget extends StatelessWidget {
const ExampleWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ExampleChangeNotifier>(
create: (_) => ExampleChangeNotifier.instance(),
builder: (_, child) {
return Selector<ExampleChangeNotifier, NetworkStatus>(
selector: (_, model) => model.networkStatus,
builder: (_, nStatus, __) => nStatus == NetworkStatus.Loading
? const Center(
child: CircularProgressIndicator(),
)
: nStatus == NetworkStatus.Error
? const Center(
child: Text('Your error widget'),
)
: Selector<ExampleChangeNotifier, int>(
selector: (_, model) => model.listLength,
builder: (_, length, __) => length > 0
? ListView.builder(
itemCount: length + 1,
itemBuilder: (context, index) {
if (index < length) {
var listItem = _.read<ExampleChangeNotifier>().list[index];
return SizedBox(
height: 60,
child: Text('List item: ${listItem.whatever}'),
);
} else {
return Center(
child: ElevatedButton(
onPressed: () {
_.read<ExampleChangeNotifier>().loadMore();
},
child: Selector<ExampleChangeNotifier, bool>(
selector: (_, model) => model.loadMoreRequest,
builder: (_, value, __) => value ? const Text('loading...') : const Text('load more'),
),
),
);
}
},
)
: const Center(
child: Text('No data found'),
),
),
);
},
);
}
}
enum NetworkStatus { Loading, Done, Error }
class ExampleChangeNotifier extends ChangeNotifier {
NetworkStatus _networkStatus = NetworkStatus.Loading;
NetworkStatus get networkStatus => _networkStatus;
final List<dynamic> _list = <dynamic>[];
List<dynamic> get list => _list;
int _listLength = 0;
int get listLength => _listLength;
int _skip = 0; //Send this in your request parameters and use for pagination, e.g (for mysql query) => ... DESC LIMIT _skip, 10
bool _loadMoreRequest = false;
bool get loadMoreRequest => _loadMoreRequest;
ExampleChangeNotifier.instance() {
_getDataFromNetwork();
}
Future<void> _getDataFromNetwork() async {
try {
//Make your http request
// For example : await http.get('https:example.com?skip=$_skip');
_loadMoreRequest = false;
// ... Parse your data
List<dynamic> networkData = <dynamic>[];
_networkStatus = NetworkStatus.Done;
if (networkData.isNotEmpty) {
for (var item in networkData) {
_list.add(item);
_listLength++;
}
}
notifyListeners();
} catch (e) {
_loadMoreRequest = false;
_networkStatus = NetworkStatus.Error;
notifyListeners();
}
}
Future<void> loadMore() async {
_skip = _listLength;
_loadMoreRequest = true;
notifyListeners();
await _getDataFromNetwork();
}
}