custom_bloc 0.1.9 custom_bloc: ^0.1.9 copied to clipboard
A simple wrapper around stream controller/rxdart stream for easier implementation of bloc pattern
A simple custom stream builder library based on BLOC pattern. Uses stream underneath.
Features #
This allows easy adding of data from network and disposing of bloc
How To Use Custom Bloc #
void main() async {
String type = 'multi';
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp(
home: type == 'one'
? const Example()
: type == 'two'
? const Example2()
: const ExampleMulti(),
));
}
class Example extends StatefulWidget {
const Example({Key? key}) : super(key: key);
@override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
final counterBloc = CounterBloc();
@override
void dispose() {
super.dispose();
counterBloc.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: CustomStreamBuilder(
stream: counterBloc.behaviorSubject,
dataBuilder: (context, data) {
return ListView.separated(
itemCount: 1,
scrollDirection: Axis.horizontal,
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 24),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'index: $index: data: $data',
style: const TextStyle(fontSize: 34),
),
],
));
},
separatorBuilder: (context, index) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: VerticalDivider(),
);
},
);
},
loadingBuilder: (context) => const Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, error) => Text(
error,
style: const TextStyle(),
),
),
),
const SizedBox(
height: 34,
),
Row(
children: [
TextButton(
onPressed: () {
counterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.resetData();
},
child: const Text(
'Set to no data',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error',
style: TextStyle(),
)),
],
),
const SizedBox(
height: 34,
)
],
),
),
);
}
}
class CounterBloc extends BaseBloc<int, String> {
int value = 0;
CounterBloc() {
fetchCurrent(false);
}
fetchCurrent(bool addError) async {
setAsLoading();
if (!addError) {
await Future.delayed(const Duration(seconds: 2));
value++;
addToModel(value);
} else {
value = 0;
addToError('Could not fetch data');
}
}
resetData() {
value = 0;
setAsNoContent();
}
invalidate() {
invalidateBaseBloc();
}
dispose() {
disposeBaseBloc();
}
}
class Example2 extends StatefulWidget {
const Example2({Key? key}) : super(key: key);
@override
State<Example2> createState() => _Example2State();
}
class _Example2State extends State<Example2> {
final counterBloc = CounterBloc();
final newCounterBloc = CounterBloc2();
@override
void dispose() {
super.dispose();
counterBloc.dispose();
newCounterBloc.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: CustomStreamBuilder.twoSubject(
streams: [
counterBloc.behaviorSubject,
newCounterBloc.behaviorSubject
],
dataBuilder2: (context, BaseModel<int?, String?> data,
BaseModel<double?, String?> secondData) {
var counterBlocData = data.model;
var counterBlocState = data.itemState;
var counterBlocDataError = data.error;
var newCounterBlocData = secondData.model;
var newCounterBlocState = secondData.itemState;
var newCounterBlocError = secondData.error;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (counterBlocState == ItemState.loading)
const Center(
child: CircularProgressIndicator(),
)
else if (counterBlocState == ItemState.hasError)
Text(
'counterBlocData error: $counterBlocDataError',
style: const TextStyle(fontSize: 34),
)
else if (counterBlocState == ItemState.noContent)
const SizedBox()
else
Text(
'counterBlocData: $counterBlocData',
style: const TextStyle(fontSize: 34),
),
const SizedBox(
height: 34,
),
if (newCounterBlocState == ItemState.loading)
const Center(
child: CircularProgressIndicator(),
)
else if (newCounterBlocState == ItemState.hasError)
Text(
'newCounterBlocError error: $newCounterBlocError',
style: const TextStyle(fontSize: 34),
)
else if (newCounterBlocState == ItemState.noContent)
const SizedBox()
else
Text(
'newCounterBlocData: $newCounterBlocData',
style: const TextStyle(fontSize: 34),
),
],
));
},
loadingBuilder: (context) => const Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, error) => Text(
error.toString(),
style: const TextStyle(),
),
),
),
const SizedBox(
height: 34,
),
Wrap(
children: [
TextButton(
onPressed: () {
counterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value to stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.resetData();
},
child: const Text(
'Set to no data stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error to stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value to stream 2',
style: TextStyle(color: Colors.red),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.resetData();
},
child: const Text(
'Set to no data stream 2',
style: TextStyle(color: Colors.red),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error to stream 2',
style: TextStyle(color: Colors.red),
)),
],
),
const SizedBox(
height: 34,
)
],
),
),
);
}
}
class CounterBloc2 extends BaseBlocInfiniteLoadingFactoryBloc<double, String> {
double value = 0;
fetchCurrent(bool addError) async {
setAsLoading();
if (!addError) {
await Future.delayed(const Duration(seconds: 2));
value += 0.5;
addToModel(value);
} else {
value = 0;
addToError('Could not fetch data');
}
}
@override
fetchNextPage(
{Function()? onSuccessfulFetch, Function()? onFailedFetch}) async {
await Future.delayed(const Duration(seconds: 2));
value += 0.5;
addToModel(value);
}
resetData() {
value = 0;
setAsNoContent();
}
invalidate() {
invalidateBaseBloc();
}
dispose() {
disposeBaseBloc();
}
}
class ExampleMulti extends StatefulWidget {
const ExampleMulti({Key? key}) : super(key: key);
@override
State<ExampleMulti> createState() => _ExampleMultiState();
}
class _ExampleMultiState extends State<ExampleMulti> {
final counterBloc = CounterBloc();
final newCounterBloc = CounterBloc2();
final newCounterBloc3 = CounterBloc3();
@override
void dispose() {
super.dispose();
counterBloc.dispose();
newCounterBloc.dispose();
newCounterBloc3.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: CustomStreamBuilder.multiSubject(
streams: [
counterBloc.behaviorSubject,
newCounterBloc.behaviorSubject,
newCounterBloc3.behaviorSubject
],
itemBuilderMulti: (context, data) {
var counterBlocData = data.first.model as int?;
var counterBlocState = data.first.itemState;
var counterBlocDataError = data.first.error as String?;
var newCounterBlocData = data.elementAt(1).model as double?;
var newCounterBlocState = data.elementAt(1).itemState;
var newCounterBlocError = data.elementAt(1).error as String?;
var newCounterBlocData3 = data.elementAt(2).model as double?;
var newCounterBlocState3 = data.elementAt(2).itemState;
var newCounterBlocError3 = data.elementAt(2).error as String?;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (counterBlocState == ItemState.loading)
const Center(
child: CircularProgressIndicator(),
)
else if (counterBlocState == ItemState.hasError)
Text(
'counterBlocData error: $counterBlocDataError',
style: const TextStyle(fontSize: 34),
)
else if (counterBlocState == ItemState.noContent)
const SizedBox()
else
Text(
'counterBlocData: $counterBlocData',
style: const TextStyle(fontSize: 34),
),
const SizedBox(
height: 34,
),
if (newCounterBlocState == ItemState.loading)
const Center(
child: CircularProgressIndicator(),
)
else if (newCounterBlocState == ItemState.hasError)
Text(
'newCounterBlocError error: $newCounterBlocError',
style: const TextStyle(fontSize: 34),
)
else if (newCounterBlocState == ItemState.noContent)
const SizedBox()
else
Text(
'newCounterBlocData: $newCounterBlocData',
style: const TextStyle(fontSize: 34),
),
if (newCounterBlocState3 == ItemState.loading)
const Center(
child: CircularProgressIndicator(),
)
else if (newCounterBlocState3 == ItemState.hasError)
Text(
'newCounterBlocError error: $newCounterBlocError3',
style: const TextStyle(fontSize: 34),
)
else if (newCounterBlocState3 == ItemState.noContent)
const SizedBox()
else
Text(
'newCounterBlocData: $newCounterBlocData3',
style: const TextStyle(fontSize: 34),
),
],
));
},
),
),
const SizedBox(
height: 34,
),
Wrap(
children: [
TextButton(
onPressed: () {
counterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value to stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.resetData();
},
child: const Text(
'Set to no data stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error to stream 1',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value to stream 2',
style: TextStyle(color: Colors.red),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.resetData();
},
child: const Text(
'Set to no data stream 2',
style: TextStyle(color: Colors.red),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error to stream 2',
style: TextStyle(color: Colors.red),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc3.fetchCurrent(false);
},
child: const Text(
'Add Value to stream 3',
style: TextStyle(color: Colors.purple),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc3.resetData();
},
child: const Text(
'Set to no data stream 3',
style: TextStyle(color: Colors.purple),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
newCounterBloc3.fetchCurrent(true);
},
child: const Text(
'Add Error to stream 3',
style: TextStyle(color: Colors.purple),
)),
],
),
const SizedBox(
height: 34,
)
],
),
),
);
}
}
class CounterBloc3 extends BaseBloc<double, String> {
double value = 0;
CounterBloc3() {
fetchCurrent(false);
}
fetchCurrent(bool addError) async {
setAsLoading();
if (!addError) {
await Future.delayed(const Duration(seconds: 2));
value += 0.5;
addToModel(value);
} else {
value = 0;
addToError('Could not fetch data');
}
}
resetData() {
value = 0;
setAsNoContent();
}
invalidate() {
invalidateBaseBloc();
}
dispose() {
disposeBaseBloc();
}
}
class ExampleCustomBlocBuilder extends StatefulWidget {
const ExampleCustomBlocBuilder({Key? key}) : super(key: key);
@override
State<ExampleCustomBlocBuilder> createState() =>
_ExampleCustomBlocBuilderState();
}
class _ExampleCustomBlocBuilderState extends State<ExampleCustomBlocBuilder> {
final counterBloc = CounterBloc();
@override
void dispose() {
super.dispose();
counterBloc.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: CustomBlocBuilder(
stream: counterBloc.behaviorSubject,
builder: (context, itemState, data, error) {
if (itemState == ItemState.hasData) {
return ListView.separated(
itemCount: 1,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 24),
itemBuilder: (context, index) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'index: $index: data: $data',
style: const TextStyle(fontSize: 34),
),
],
));
},
separatorBuilder: (context, index) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: VerticalDivider(),
);
},
);
} else if (itemState == ItemState.loading) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (itemState == ItemState.loading) {
return Text(
error ?? '',
style: const TextStyle(),
);
}
return const SizedBox();
}),
),
const SizedBox(
height: 34,
),
Row(
children: [
TextButton(
onPressed: () {
counterBloc.fetchCurrent(false);
},
child: const Text(
'Add Value',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.resetData();
},
child: const Text(
'Set to no data',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.fetchCurrent(true);
},
child: const Text(
'Add Error',
style: TextStyle(),
)),
],
),
const SizedBox(
height: 34,
)
],
),
),
);
}
}
class CounterInfiniteScrollingBloc
extends BaseBlocInfiniteLoadingFactoryBloc<double, String> {
int get page => pageNumber;
int get itemsPerPage => perPage;
double value = 0;
@override
initFetch() async {
super.initFetch();
setAsLoading();
await Future.delayed(const Duration(seconds: 2));
value += 0.5;
addToModel(value);
incrementPageCountAndStartLoaderSubject();
}
@override
fetchNextPage(
{Function()? onSuccessfulFetch, Function()? onFailedFetch}) async {
bool isSuccessful = await Future.delayed(const Duration(seconds: 2));
if (isSuccessful) {
value += 0.5;
addToModel(value);
incrementPageCountAndStartLoaderSubject();
} else {
stopLoaderSubject();
}
}
resetData() {
value = 0;
setAsNoContent();
}
invalidate() {
invalidateBaseBloc();
}
dispose() {
disposeBaseBloc();
}
}
class ExampleInfiniteScrolling extends StatefulWidget {
const ExampleInfiniteScrolling({Key? key}) : super(key: key);
@override
State<ExampleInfiniteScrolling> createState() =>
_ExampleInfiniteScrollingState();
}
class _ExampleInfiniteScrollingState extends State<ExampleInfiniteScrolling> {
final counterBloc = CounterInfiniteScrollingBloc();
@override
void dispose() {
super.dispose();
counterBloc.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: CustomInfiniteScrollingStreamBuilder(
loaderStream: counterBloc.loaderSubject,
builder: (context, loadingData) => CustomStreamBuilder(
stream: counterBloc.behaviorSubject,
dataBuilder: (context, data) {
return NotificationListener(
onNotification: (notification) {
if (notification is ScrollNotification) {
counterBloc.sink.add(notification);
}
return true;
},
child: ListView.separated(
itemCount: 1,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 24),
itemBuilder: (context, index) {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'index: $index: data: $data',
style: const TextStyle(fontSize: 34),
),
],
));
},
separatorBuilder: (context, index) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: VerticalDivider(),
);
},
),
);
},
loadingBuilder: (context) => const Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, error) => Text(
error,
style: const TextStyle(),
),
),
),
),
const SizedBox(
height: 34,
),
Row(
children: [
TextButton(
onPressed: () {
counterBloc.fetchNextPage();
},
child: const Text(
'Add Value',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.resetData();
},
child: const Text(
'Set to no data',
style: TextStyle(),
)),
const SizedBox(
width: 34,
),
TextButton(
onPressed: () {
counterBloc.fetchNextPage();
},
child: const Text(
'Add Error',
style: TextStyle(),
)),
],
),
const SizedBox(
height: 34,
)
],
),
),
);
}
}
copied to clipboard