I am new in using bloc library with freezed package. I have a scenario where a list of objects is coming from API is displayed. Now the list tile has a Marked as Fav button and clicking upon it a event is trigged and fav bool is toggled and state is emitted.
The Issue :
The value of the object is changed but the UI is not updated accordingly.
Main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_bloc_update/bloc/fakeapi_bloc.dart';
void main() {
runApp(
BlocProvider(
create: (context) => FakeapiBloc(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: SafeArea(child: MyHomePage()),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
context.read<FakeapiBloc>().add(const FakeapiEvent.loadData());
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<FakeapiBloc, FakeapiState>(
builder: (context, state) {
return state.map(
inital: (_) => const Center(
child: CircularProgressIndicator(),
),
loading: (_) => const Center(
child: CircularProgressIndicator(),
),
loaded: (state) {
return ListView.builder(itemBuilder: (ctx, pos) {
return ListTile(
title: Text(state.posts[pos].title),
trailing: IconButton(
onPressed: () {
context
.read<FakeapiBloc>()
.add(FakeapiEvent.markedFav(pos: pos));
},
icon: Icon(state.posts[pos].isFav
? Icons.favorite
: Icons.favorite_border_outlined),
),
);
});
},
error: (_) => const Center(
child: CircularProgressIndicator(),
),
);
},
);
}
}
Post.dart
import 'dart:convert';
List<Post> postFromJson(String str) =>
List<Post>.from(json.decode(str).map((x) => Post.fromJson(x)));
String postToJson(List<Post> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Post {
Post({
required this.userId,
required this.id,
required this.title,
required this.body,
});
final int userId;
final int id;
final String title;
final String body;
bool isFav = false;
factory Post.fromJson(Map<String, dynamic> json) => Post(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"],
);
Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"body": body,
};
}
bloc.dart
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:freezed_bloc_update/post.dart';
import 'package:http/http.dart' as http;
part 'fakeapi_event.dart';
part 'fakeapi_state.dart';
part 'fakeapi_bloc.freezed.dart';
class FakeapiBloc extends Bloc<FakeapiEvent, FakeapiState> {
FakeapiBloc() : super(const FakeapiState.inital()) {
on<LoadData>(
(event, emit) async {
try {
emit(const FakeapiState.loading());
Uri uri = Uri.parse('https://jsonplaceholder.typicode.com/posts');
final response = await http.get(uri);
if (response.statusCode == 200) {
emit(FakeapiState.loaded(posts: postFromJson(response.body)));
} else {
emit(const FakeapiState.error(errorMessage: 'Api Call Failed'));
}
} catch (err) {
emit(const FakeapiState.error(errorMessage: 'Api Call Failed'));
}
},
);
on<MarkedFav>((event, emit) {
final previousState = state as Loaded;
previousState.posts[event.pos].isFav =
!previousState.posts[event.pos].isFav;
emit(FakeapiState.loaded(posts: [...previousState.posts]));
});
}
}
events.dart
part of 'fakeapi_bloc.dart';
#freezed
class FakeapiEvent with _$FakeapiEvent {
const factory FakeapiEvent.loadData() = LoadData;
const factory FakeapiEvent.markedFav({required int pos}) = MarkedFav;
}
states.dart
part of 'fakeapi_bloc.dart';
#freezed
class FakeapiState with _$FakeapiState {
const factory FakeapiState.inital() = Initial;
const factory FakeapiState.loading() = Loading;
const factory FakeapiState.loaded({required List<Post> posts}) = Loaded;
const factory FakeapiState.error({required String errorMessage}) = Error;
}
One solution which I did was keeping a bool variable(outside of model class) in state itself and clicking upon the fav toggling the bool value. Which is retriggering the UI update.
your Post class should be converted to a freezed class
Related
Introduction: I am encountering an unexpected problem in my code when using BLoC, which I don't know how to fix, and I was looking for a solution.
When I use the code below, an object adds to the ListView.builder whenever I click on the ListTile as expected. However, This addition only happens once and when I continue to press on the ListTile to add more items to the list, the state is not updated (which is verifiable by pressing the Hot Reload button, which then updates the table with the items).
Problem: I have converted the MyHomePage widget to a StatefulWidget and wrapped the call to my bloc in a setState method (uncomment the commented section of the code to see that it works as expected). Now, as I understand it, I should be able to use BLoC for my state management needs and not need to use a StatefulWidget.
Questions:
Why is my code not working correctly?
How can I change my code to fix my problem?
Code:
Here is the complete minimum code, which reflects the problem that I am having:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CollectionBloc(),
child: const MaterialApp(
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return BlocBuilder<CollectionBloc, Collection>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: ListView.builder(
itemCount: state.hierarchy(state).length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
var showType = state.hierarchy(state)[index].showType;
// TODO: setState function is here.
setState(() {
if (showType == ShowType.collection) {
BlocProvider.of<CollectionBloc>(context).add(AddSeries(
series: Collection(
name:
"Series ${state.getChildrenOfNode(state.hierarchy(state)[index]).length + 1}",
showType: ShowType.series,
children: [],
)));
BlocProvider.of<CollectionBloc>(context).add(UpdateBloc(collection: state));
}
if (showType == ShowType.series) {
BlocProvider.of<CollectionBloc>(context).add(AddSeason(
series: state.hierarchy(state)[index],
season: Collection(
name:
'Season ${state.getChildrenOfNode(state.hierarchy(state)[index]).length + 1}',
showType: ShowType.season,
children: [],
)));
}
if (showType == ShowType.season) {
BlocProvider.of<CollectionBloc>(context).add(AddEpisode(
season: state.hierarchy(state)[index],
episode: Collection(
name:
"Episode ${state.getChildrenOfNode(state.hierarchy(state)[index]).length + 1}",
showType: ShowType.episode,
children: [],
)));
}
});
},
leading: Card(
child: TextWidget(name: state.hierarchy(state)[index].name),
),
);
},
),
floatingActionButton: FloatingActionButton(
child: const Text('to json'),
onPressed: () {
var toJson = state.toJson();
var fromJson = Collection.fromJson(toJson);
print(fromJson);
},
),
);
},
);
}
}
class TextWidget extends StatelessWidget {
const TextWidget({super.key, required this.name});
final String name;
#override
Widget build(BuildContext context) {
return Text(name);
}
}
/// BLoC
class InitialState extends Collection {
InitialState(collection)
: super(
name: collection.name,
showType: collection.showType,
children: collection.children,
);
}
abstract class BLoCEvents {}
class AddSeries extends BLoCEvents {
AddSeries({required this.series});
final Collection series;
}
class AddSeason extends BLoCEvents {
AddSeason({required this.series, required this.season});
final Collection series;
final Collection season;
}
class AddEpisode extends BLoCEvents {
AddEpisode({required this.season, required this.episode});
final Collection season;
final Collection episode;
}
class UpdateBloc extends BLoCEvents {
UpdateBloc({required this.collection});
final Collection collection;
}
class CollectionBloc extends Bloc<BLoCEvents, InitialState> {
CollectionBloc()
: super(InitialState(Collection(
name: 'Collection', showType: ShowType.collection, children: []))) {
on<AddSeries>(
((event, emit) => emit(state..addSeries(series: event.series))));
on<AddSeason>(((event, emit) =>
emit(state..addSeason(series: event.series, season: event.season))));
on<AddEpisode>(((event, emit) =>
emit(state..addEpisode(season: event.season, episode: event.episode))));
///todo: update bloc here.
on<UpdateBloc>(((event, emit) => print(state.toJson())));
}
}
/// Model
enum ShowType { collection, series, season, episode }
class Collection {
Collection(
{required this.name, required this.showType, required this.children});
final String name;
final ShowType showType;
List<Collection> children = [];
void addSeries({required Collection series}) => children.add(series);
void addSeason({required Collection series, required Collection season}) =>
series.children.add(season);
void addEpisode({required Collection season, required Collection episode}) =>
season.children.add(episode);
List<Collection> hierarchy(Collection node) {
List<Collection> list = [];
list.add(node);
for (Collection child in node.children) {
list.addAll(hierarchy(child));
}
return list;
}
List<Collection> getChildrenOfNode(Collection node) {
List<Collection> list = [];
for (Collection child in node.children) {
list.add(child);
}
return list;
}
toJson() {
return {
'name': name,
'showType': showType,
'children': children.map((child) => child.toJson()).toList(),
};
}
factory Collection.fromJson(Map<String, dynamic> json) {
return Collection(
name: json['name'],
showType: json['showType'],
children: json['children']
.map<Collection>((child) => Collection.fromJson(child))
.toList());
}
#override
String toString() {
return 'Collection{name: $name, showType: $showType, children: $children}';
}
}
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:developer' as devtools;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => PersonsBloc(),
child: const MyHomePage(),
),
);
}
}
#immutable
abstract class LoadAction {
const LoadAction();
}
#immutable
class LoadPersonsAction implements LoadAction {
final PersonsUrl url;
const LoadPersonsAction({required this.url}) : super();
}
enum PersonsUrl {
persons1,
persons2,
}
extension Subscript<T> on Iterable<T> {
T? operator [](int index) => length > index ? elementAt(index) : null;
}
extension UrlString on PersonsUrl {
String get getUrl {
switch (this) {
case PersonsUrl.persons1:
devtools.log("Getting person1 url");
return "http://127.0.0.1:5500/app_2/api/persons1.json";
case PersonsUrl.persons2:
devtools.log("Getting person2 url");
return "http://127.0.0.1:5500/app_2/api/persons2.json";
}
}
}
#immutable
class Person {
final String name;
final int age;
const Person({required this.name, required this.age});
Person.fromJson(Map<String, dynamic> json)
: name = json['name'] as String,
age = json['age'] as int;
}
Future<Iterable<Person>> getPersons(String url) => HttpClient()
.getUrl(Uri.parse(url))
.then((req) => req.close())
.then((resp) => resp.transform(utf8.decoder).join())
.then((str) => json.decode(str) as List<dynamic>)
.then((value) => value.map((e) => Person.fromJson(e)));
#immutable
class FetchResult {
final Iterable<Person> persons;
final bool isRetrievedFromCache;
const FetchResult(
{required this.persons, required this.isRetrievedFromCache});
#override
String toString() =>
'FetchResult (isRetrievedFromCache = $isRetrievedFromCache, persons = $persons';
}
class PersonsBloc extends Bloc<LoadAction, FetchResult?> {
final Map<PersonsUrl, Iterable<Person>> _cache = {};
PersonsBloc() : super(null) {
on<LoadPersonsAction>((event, emit) async {
final url = event.url;
devtools.log("In bloc :$url");
if (_cache.containsKey(url)) {
devtools.log("Present in cache");
final cachedPersons = _cache[url]!;
final result = FetchResult(
persons: cachedPersons,
isRetrievedFromCache: true,
);
emit(result);
} else {
devtools.log("Not present in cache");
final persons = await getPersons(url.getUrl);
if (persons.isNotEmpty) {
print("Persons is there");
} else {
print("Persons is not there");
}
_cache[url] = persons;
final result =
FetchResult(persons: persons, isRetrievedFromCache: false);
emit(result);
}
});
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BLoC App 2'),
),
body: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
devtools.log("Clicked to get person1");
context.read<PersonsBloc>().add(
const LoadPersonsAction(url: PersonsUrl.persons1));
},
child: const Text('Load Json #1')),
TextButton(
onPressed: () {
devtools.log("Clicked to get person2");
context.read<PersonsBloc>().add(
const LoadPersonsAction(url: PersonsUrl.persons2));
},
child: const Text('Load Json #2')),
],
),
BlocBuilder<PersonsBloc, FetchResult?>(
buildWhen: ((previous, current) {
return previous?.persons != current?.persons;
}),
builder: ((context, state) {
final persons = state?.persons;
if (persons == null) {
return const SizedBox();
}
return ListView.builder(
itemCount: persons.length,
itemBuilder: ((context, index) {
devtools.log('rendering list tiles');
final person = persons[index]!;
return ListTile(
leading: Text(person.age.toString()),
title: Text(person.name),
);
}));
}),
)
],
));
}
}
I am learning BLoC State Management from Vandad Nahavandipoor's State Management Playlist, when I click on the Load Json #1 button, it goes until the getPersons() function is called, but then doesn't fetch any data to return. Instead, it shows me some errors with no description whatsoever.
ERROR:
enter image description here
Here is the error I get after I Hot Restart:
E/flutter ( 2894): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: SocketException: Connection timed out (OS Error:
Connection timed out, errno = 110), address = 192.168.0.1, port =
51180
I'm learning bloc from a deprecated tutorial and I have troubles with making it work.
The error which I receive is:
Error while creating GetRandomNumberTrivia
(...)
The following StateError was thrown building InheritedProvider<NumberTriviaBloc>:
Bad state: Tried to read a provider that threw during the creation of its value.
The exception occurred during the creation of type NumberTriviaBloc.
Full error: https://pastebin.com/CpvrFZ1v
This is my bloc file:
const String serverFailureMessage = 'Server failure';
const String cacheFailureMessage = 'Cache failure';
const String invalidInputMessage =
'Invalid input, the number should be positive integer or zero';
class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> {
final GetConcreteNumberTrivia getConcreteNumberTrivia;
final GetRandomNumberTrivia getRandomNumberTrivia;
final InputConverter inputConverter;
NumberTriviaBloc(
{required this.getConcreteNumberTrivia,
required this.getRandomNumberTrivia,
required this.inputConverter})
: super(Empty()) {
on<GetTriviaForConcreteNumber>(
(event, emit) async {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
inputEither.fold((l) => emit(Error(message: invalidInputMessage)),
(r) async {
emit(Loading());
final failureOrTrivia = await getConcreteNumberTrivia(Params( number: r));
//todo message
failureOrTrivia.fold((l) => emit(Error(message: 'problem message')),
(r) => emit(Loaded(trivia: r)));
});
},
);
on<GetTriviaForRandomNumber>(
(event, emit) async {
emit(Loading());
final failureOrTrivia = await getRandomNumberTrivia(NoParams());
failureOrTrivia.fold((l) => emit(Error(message: "Invalid input")),
(r) async {
emit(Loaded(trivia: r));
});
},
);
}
}
Here's code which I mimic: https://github.com/ResoCoder/flutter-tdd-clean-architecture-course/blob/master/lib/features/number_trivia/presentation/bloc/number_trivia_bloc.dart
That's how I'm using BlocProvider:
class NumberTriviaPage extends StatelessWidget {
const NumberTriviaPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
(...)
body: buildBody(context),
);
}
}
BlocProvider<NumberTriviaBloc> buildBody(BuildContext context) {
return BlocProvider(
create: (_) => sl<NumberTriviaBloc>(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: <Widget>[
const SizedBox(height: 10),
BlocBuilder<NumberTriviaBloc, NumberTriviaState>(
builder: (context, state) {
if (state is Empty) {
return const MessageDisplay(
message: 'Start searching!',
);
} else if (state is Loading) {
return const LoadingWidget();
} else if (state is Loaded) {
return TriviaDisplay(numberTrivia: state.trivia);
} else if (state is Error) {
return MessageDisplay(
message: state.message);
}else{
return Container();
}
},
),
const TriviaControls(),
],
),
),
),
);
}
And that's how I init bloc, by using dependency injection:
import 'injection_container.dart' as di;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await di.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
(...)
home: const NumberTriviaPage(),
);
}
}
//sl = serviceLocator
final sl = GetIt.instance;
Future<void> init() async {
//serviceLocator call looking for are registered types/dependencies
sl.registerFactory(() => NumberTriviaBloc(getConcreteNumberTrivia: sl(), getRandomNumberTrivia: sl(), inputConverter: sl()));
(...)
}
According to error the problem seems to lays somewhere in GetRandomNumberTrivia, but I implemented it in same way as similar GetConcreteNumberTrivia and I do not see what can be wrong here.
In class GetRandomNumberTrivia I added typing NumberTriviaRepository for the field repository and the code compiled.
I am new to Flutter, and bloc too. I got the idea, how bloc works. But When I create a simple app as the first step of my note app. The bloc doesn't give the data. This simple app has two screens. list screen and Notedetailscreen. Button in NoteDetailScreen tapped, data does not print to the text widget.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
import 'package:note_demo_bloc/list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<NoteBloc>(
create: (context) => NoteBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ListScreen(),
),
);
}
}
note_bloc.dart
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart';
part 'note_event.dart';
part 'note_state.dart';
class NoteBloc extends Bloc<NoteblocEvent, NoteblocState> {
NoteBloc() : super(NoteblocInitial());
#override
Stream<NoteblocState> mapEventToState(
NoteblocEvent event,
) async* {
if (event == NoteSaveEvent) {
yield NoteSaveState(state);
}
}
}
part of 'note_bloc.dart';
#immutable
abstract class NoteblocEvent {}
class NoteSaveEvent extends NoteblocEvent {
NoteSaveEvent(this.text);
final text;
}
note_state.dart
part of 'note_bloc.dart';
#immutable
abstract class NoteblocState {}
class NoteblocInitial extends NoteblocState {}
class NoteSaveState extends NoteblocState {
NoteSaveState(this.text);
final text;
}
list_screen.dart
import 'package:flutter/material.dart';
import 'package:note_demo_bloc/note_detail_screen.dart';
class ListScreen extends StatefulWidget {
const ListScreen({Key? key}) : super(key: key);
#override
_ListScreenState createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('hi'),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NoteDetailScreen(),
),
);
},
),
);
}
}
Note_detailscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
class NoteDetailScreen extends StatefulWidget {
const NoteDetailScreen({Key? key}) : super(key: key);
#override
_NoteDetailScreenState createState() => _NoteDetailScreenState();
}
class _NoteDetailScreenState extends State<NoteDetailScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<NoteBloc>(context).add(NoteSaveEvent('hi'));
},
child: Text('click'),
),
BlocBuilder<NoteBloc, NoteblocState>(
builder: (context, state) {
return Text(state.toString());
},
)
],
),
);
}
}
Your bloc, state, and event looks fine. When you push screen you might need to use BlocProvider again. So try this:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
import 'package:note_demo_bloc/list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
NoteBloc _noteBloc = NoteBloc();
#override
Widget build(BuildContext context) {
return BlocProvider<NoteBloc>(
create: (context) => _noteBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ListScreen(),
),
);
}
}
list_screen.dart
import 'package:flutter/material.dart';
import 'package:note_demo_bloc/note_detail_screen.dart';
class ListScreen extends StatefulWidget {
const ListScreen({Key? key}) : super(key: key);
#override
_ListScreenState createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('hi'),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(value: BlocProvider.of<NoteBloc>(context), child: NoteDetailScreen()),
),
);
},
),
);
}
}
Note_detailscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
class NoteDetailScreen extends StatefulWidget {
const NoteDetailScreen({Key? key}) : super(key: key);
#override
_NoteDetailScreenState createState() => _NoteDetailScreenState();
}
class _NoteDetailScreenState extends State<NoteDetailScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<NoteBloc>(context).add(NoteSaveEvent('hi'));
},
child: Text('click'),
),
BlocBuilder<NoteBloc, NoteblocState>(
bloc: BlocProvider.of<NoteBloc>(context),
builder: (context, state) {
return Text(state.toString());
},
)
],
),
);
}
}
So, this is not an answer of your question but consider that as alternative (for future users of SO).
As state management is a free choice, and everyone could manage that as it’s “modus operandi“ this helper class “home made” could be a good choice.
import 'dart:async';
import 'dart:core';
class Method {
Method(this.name, this.params);
final String name;
final Map<String, Object> params;
}
class _Controller {
_Controller._();
static final Map<String, _Controller> _this = new Map<String, _Controller>();
final Map<String, Function(Method)> _funcs = new Map<String, Function(Method)>();
factory _Controller(String identifier) => _this.putIfAbsent(identifier, () => _Controller._());
Future<void> activateListener(String listenerId, Function(Method) function) async {
if (function != null)
_funcs.containsKey(listenerId) ? _funcs[listenerId] = function : _funcs.putIfAbsent(listenerId, () => function);
}
Future<void> deactivateListener(String listenerId) async =>
_funcs.removeWhere((String key, Function(Method) func) => key == listenerId);
Future<void> removeListener(String identifier) async =>
_this.removeWhere((String key, _Controller mClass) => key == identifier);
Future<void> callMethod(String methodName, {Map<String, Object> params}) async =>
Future.forEach(_funcs.values.where((v) => v != null), (func) async => func.call(Method(methodName, params)));
}
mixin MethodListener on Object {
_Controller _getController(String identifier) => _Controller(identifier ?? this.runtimeType.toString());
Future<void> activateListener({String identifier, List<String> identifiers}) async {
if (identifiers != null && identifiers.length > 0)
identifiers.forEach(
(String currentId) => _getController(currentId).activateListener(this.hashCode.toString(), onMethodListener));
else
_getController(identifier).activateListener(this.hashCode.toString(), onMethodListener);
}
Future<void> deactivateListener({String identifier, List<String> identifiers}) async {
if (identifiers != null && identifiers.length > 0)
identifiers.forEach((String currentId) => _getController(currentId).deactivateListener(this.hashCode.toString()));
else
_getController(identifier).deactivateListener(this.hashCode.toString());
}
Future<void> removeListener({String identifier}) async => _getController(identifier).removeListener(identifier);
void onMethodListener(Method method) async => null;
Future<void> callMethodOn(String identifier, String methodName, {Map<String, Object> params}) async =>
_getController(identifier).callMethod(methodName, params: params);
}
class MethodManager with MethodListener {
MethodManager._();
static MethodManager _this;
factory MethodManager() {
if (_this == null) _this = MethodManager._();
return _this;
}
Future<void> callMethodOnWidgets(List<String> identifiers, String methodName, {Map<String, Object> params}) async =>
identifiers.forEach((String currentId) => callMethodOn(currentId, methodName, params: params));
#override
Future<void> callMethodOn(String identifier, String methodName, {Map<String, Object> params}) async =>
super.callMethodOn(identifier, methodName, params: params);
}
then you can implements classes with “with MethodListener” as follows:
import 'package:flutter/material.dart';
import 'package:yourpackagehere/utils/XMethods.dart';
class Test extends StatefulWidget {
static const String NAME = "Test";
#override
createState() => _TestState();
}
class _TestState extends State<Test> with MethodListener {
String _ciao;
#override
void initState() {
super.initState();
this.activateListener(identifier: Test.NAME);
}
#override
void dispose() {
this.deactivateListener(identifier: Test.NAME);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(child: Text(_ciao));
}
#override
void onMethodListener(Method method) {
switch (method.name) {
case "say_hello":
if (mounted) {
setState(() {
_ciao = method.params["my_string"];
});
}
break;
}
}
}
Usage:
From everywhere (from widgets or classes):
MethodManager().callMethodOn(Test.NAME, "say_hello", params: {"my_string": "SIAMO CAMPIONI DI EUROPA!!!"});
i m in a situation where i have to implement country city state dropdown list somehow i manage things with normal flutter dropdown button and that's work ,but the issue is i can't search data in that dropdown button so user have to scroll and find there country or city which is a very bad user experience , so i found this package https://pub.dev/packages/searchable_dropdown whic is working but i m not sure how to implement that search in my code here is my code
data from fututre
import 'package:flutter/material.dart';
// import 'package:foodfromforeign1/models/country.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter_typeahead/flutter_typeahead.dart';
void main() => runApp( MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyHomePage(title: 'Users'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final String url = "http://10.0.2.2/fff/api/allcountries/";
List data = List(); //line
Future<String> getSWData() async {
var res = await http .get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody.;
});
return "Sucess";
}
#override
void initState() {
this.getSWData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child:Column(
children: <Widget>[
TypeAheadField<data>(
onSuggestionSelected: (data) => print('selected: ${data.name} ${data.id}'),
suggestionsCallback: (s) => data.where((c) => c.name.toLowerCase().contains(s.toLowerCase())),
itemBuilder: (ctx, data) => Text(data.name, textScaleFactor: 2,),
)
],
),
),
);
}
}
Another option here aside from using plugins is with the use of the Autocomplete widget. This should inflate an autocomplete list over other widgets. If you're using a List<Object> from an API response, you can map the keywords that you need to filter from to a List<String> to be used in the Autocomplete widget. The index can be used as a reference on the List<Object> to fetch the Object.
var fruits = ['Apple', 'Banana', 'Mango', 'Orange'];
_autoCompleteTextField() {
return Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return fruits.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
debugPrint('You just selected $selection');
},
);
}