Build Data Back to ListView Builder [Flutter] - database

The existing code shows a list of buttons of varying interests. Users can tap to select which interests they prefer.
However, if the user has already selected their interests beforehand and comes back to this page, it's illogical to get the users to choose from a fresh state again.
I want to repopulate what the users have previously chosen and reflect back on the screen as chosen (which = widget.viewInterest.isChosen). The color of container will be Color(0xff0B84FE), & color of text is Colors.yellow, as seen in the code below.
Let's say user has chosen this list
List UserInterests = [
"โ˜• Coffee",
"๐ŸŽญ Theaters",
];
QUESTION: How to make containers that contain these strings bool true (which is widget.viewInterest.isChosen), similar to when users have tapped on the respective buttons?
Attached is truncated code:
final List<String> artsInterests = [
"๐Ÿ“ธ Photography",
"๐ŸŽญ Theaters",
"๐Ÿ–ผ๏ธ Exhibitions",
"๐Ÿ“ Architecture",
"๐Ÿณโ€ Cooking",
"โ˜• Coffee",
"๐Ÿ–๏ธ Design",
"๐Ÿ‘— Fashion",
"๐Ÿ“š Reading",
"๐Ÿ’ƒ๐Ÿฝ Dance",
"๐Ÿบ Pottery",
"๐ŸŽจ Drawing",
"๐Ÿ’‹ Beauty",
"๐Ÿ“– Journalling",
];
StatelessWidget shortened
final List<String> artsInterests;
shortened
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(1),
itemCount: artsInterests.length
itemBuilder: (context, int index) {
return Interests2(AvailableInterestChosen(
artsInterests[index],
isChosen: false,
));
brackets...
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(1),
itemCount: artsInterests.length - 7,
itemBuilder: (context, int index) {
return Interests2(AvailableInterestChosen(
artsInterests[7 + index],
isChosen: userChosenInterests
.contains(artsInterests[7 + index]),
));
closing brackets...
List<String> chosenArtsInterests = [];
List<String> UserInterests = [
"โ˜• Coffee",
"๐ŸŽญ Theaters",
];
class Interests2 extends StatefulWidget {
final AvailableInterestChosen viewInterest;
Interests2(this.viewInterest);
String id = 'Interests2';
#override
Interests2State createState() => Interests2State();
}
class Interests2State extends State<Interests2> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
Container container = Container(
decoration shortened
decoration: BoxDecoration(
color: widget.viewInterest.isChosen && chosenInterests.length < 9
? Color(0xff0B84FE)
: Colors.white.withOpacity(0.87),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.69),
spreadRadius: 1,
blurRadius: 3,
offset: Offset(0, 1), // changes position of shadow
),
],
borderRadius: BorderRadius.circular(9),
),
child: Text(
'${widget.viewInterest.title}',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: widget.viewInterest.isChosen && chosenInterests.length < 9
? Colors.white
: Colors.black),
));
if (widget.viewInterest.isChosen && chosenInterests.length < 9) {
chosenArtsInterests.add('${widget.viewInterest.title}');
print(chosenArtsInterests);
} else {
chosenArtsInterests.remove('${widget.viewInterest.title}');
print(chosenArtsInterests);
}
return GestureDetector(
onTap: () {
setState(() {
widget.viewInterest.isChosen = !widget.viewInterest.isChosen;
});
},
child: container,
);
}
}
class AvailableInterestChosen {
bool isChosen;
String title;
AvailableInterestChosen(this.title, {this.isChosen = false});
}
For the buttons to depict UI that shows they are chosen, my guess is something like
for (string i in UserInterests) setState ((){widget.viewInterest.isChosen})
But in regards to where to put it or the exact code, I'm lost. If anyone has some experience or similar resources to share so I can read on it, that will be great!

How about checking the element is in the UserInterests list?
Something like this may work,
AvailableInterestChosen(
artsInterests[index],
isChosen: UserInterests.contains(artsInterests[index]),
)

Related

How do I display fetched Data from MariaDB to a listview

So basically what i want to do is to display the data i get from my DB in a ListView, but I'm only able to show one attribute of my Object Device.
This my Class for device
class Devices {
final int iD;
final String location;
final String type;
Devices({required this.iD, required this.location, required this.type});
factory Devices.fromJson(Map<String, dynamic> json) {
return Devices(
iD: json['id'] as int,
location: json['Ort'] as String,
type: json['Typ'] as String);
}
}
The json i get from my PHP script looks like this
{"id":1,"Ort":"Wohnzimmer","Typ":"Sensor"}
Code to show my Class
class DeviceList extends StatelessWidget {
List<Devices> dList;
DeviceList({super.key, required this.dList});
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: dList.length,
itemBuilder: (context, index) {
// return Text(dList[index].location,
// style: const TextStyle(color: Colors.white, fontSize: 40));
return (ListTile(title: Text(dList[index].iD.toString())));
},
);
}
}
I want to show all attributes of my Class at the same and not only the iD of it, but i have no clue how to do it.
in ListTile widget you can display your data like this
ListView.builder(
itemCount: dList.length,
itemBuilder: (context, index) {
return Card(
elevation: 4,
child: ListTile(
leading: CircleAvatar(
child: Text("${dList[index].iD}"),
),
title: Text(dList[index].type),
subtitle: Text(dList[index].location),
),
);
},
),
which will look like this
there are a lot of other ways to display that data.
It depends on how you want to display the other attributes
if you want to use ListTile you probably want to make the use of the leading and trailing
return ListTile(
leading: Text(
dList[index].iD.toString(),
),
title: Text(
dList[index].location,
),
trailing: Text(
dList[index].type,
),
);
or use a Row inside the title, it's up to you

How to understand to write gridview

I am a bit stuck with writing code. I have two lists, one "listAll" with numbers of all computers and "listBusy", which I get from JSON, with numbers of busy computers.
I want to write code, which displayed in gridview icons (green color) of all computers, but if computer is busy, he will have different icon (red color).
Can someone help with understanding how to write right code?
Thanks for helping!
List<String> pcAll = [
'S01',
'S02',
'S03',
'S04',
'S05',
'S06',
'S07',
'S08',
'S09',
'S10',
'S11',
'S12',
'S13',
'S14',
'S15',
'S16',
'S17',
'S18',
'S19'
];
List<String> pcBusy = [];
Future fetchDataStandart() async {
final urlAuth =
Uri.parse('http://XXX.XX.XXX.XXX/api/usersessions/activeinfo');
final response = await http
.get(urlAuth, headers: <String, String>{'authorization': basicAuth});
if (response.statusCode == 200) {
List listPc = List.from(json.decode(response.body)['result']);
pcBusy.clear();
for (int i = 0; i < listPc.length; i++) {
return pcBusy.add(listPc[i]['hostName']);
}
print(pcBusy);
} else {
throw Exception('Error');
}
}
Modified my code a little, not working
lass _EvrokoStandartScreenState extends State<EvrokoStandartScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
'ะ•ะ’ะ ะžะšะž ะกั‚ะฐะฝะดะฐั€ั‚ ะทะฐะป',
),
),
body: FutureBuilder(
future: getDataStandart(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return ComputerGrid();
}
},
));
}
}
class ComputerGrid extends StatelessWidget {
const ComputerGrid();
#override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
childAspectRatio: 3 / 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),
itemCount: pcAll.length,
itemBuilder: (BuildContext context, index) {
return GridTile(
child: Container(
color: pcBusy.contains(pcAll[index]) ? Colors.red : Colors.green,
child: Text(
pcBusy[index],
),
),
);
},
);
}
}
[![Error][1]][1]
[1]: https://i.stack.imgur.com/keUZf.png
Right, you have the right tools for this job. How I would do it, is as follows.
class ComputerGrid extends StatelessWidget {
const ComputerGrid();
#override
Widget build(BuildContext context) {
/// Assuming you store your lists and other logic in some state file
return GridView.builder(
/// Options to change the layout and look of your items.
/// Play around with this a bit.
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
childAspectRatio: 3 / 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),
/// You want as many items as there are entries in your listPc list
itemCount: listPc.length,
/// Building the actual items
itemBuilder: (BuildContext context, index) {
/// My example is barebones, play around with it to customize it
return GridTile(
child: Container(
/// Change the color depending on whether the computer is in the busy list
color: pcBusy.contains(listPc[index]['hostName'])
? Colors.red
: Colors.green,
child: Text(
/// Show the number of the computer at the current index.
listPc[index]['hostName'],
),
),
);
},
);
}
}
There's also a further optimization you can make in your for loop. You seem to have a background in JavaScript, and I think the for loop in Dart is much more elegant. The for loop below does the exact same as the one you have in your own code snippet.
for (final pc in listPc) {
pcBusy.add(pc['hostName']);
continue;
}

How to check if array is null and display not available if it's null

Unfortunately my console is showing a 'The method '[]' was called on null.' error when I try to display a product page where the product doesnโ€™t have the below array as an option in Firestore. Is there a way to check if the array has a null value and display โ€˜Product Personalisation Not Available' When the itโ€™s not in Firestore. The following code works fine when the productPersonalisation list is available.
import 'package:fj_trade/constants.dart';
import 'package:flutter/material.dart';
class ProductPersonalisation extends StatefulWidget {
final List productPersonalisation;
final Function(String) onSelected;
const ProductPersonalisation({Key key, this.productPersonalisation, this.onSelected}) : super(key: key);
#override
_ProductPersonalisationState createState() => _ProductPersonalisationState();
}
class _ProductPersonalisationState extends State<ProductPersonalisation> {
int _selected = 0;
#override
Widget build(BuildContext context) {
return Row(
children: [
for(var i=0; i < widget.productPersonalisation.length; i++)
GestureDetector(
onTap: () {
widget.onSelected("${widget.productPersonalisation[i]}");
setState(() {
_selected = i;
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 0.0),
height: 42,
decoration: BoxDecoration(
color: _selected == i ? Theme.of(context).accentColor : Constants.lightGrey,
borderRadius: BorderRadius.circular(4),
),
alignment: Alignment.center,
margin: EdgeInsets.only(
right: 6.0,
),
child: Text("${widget.productPersonalisation[i]}",
style: TextStyle(
fontWeight: FontWeight.w600,
color: _selected == i ? Colors.white : Colors.black87,
),
),
),
),
],
);
}
}
You can do this if the element in the array is null
widget.onSelected("${widget.productPersonalisation[i] ?? 'Product Personalisation Not Available'}");
However before the loop, you should check that widget.productPersonalisation isn't null.
if you try to access an index of a null list it will throw an exception.
Try this:
widget.onSelected(widget.productPersonalisation != null ? "${widget.productPersonalisation[i]}" : 'Product Personalisation Not Available');

passing API data from page 1 to another dart page on flutter

page that already has data from the API, then I want to send the API data to the next page.
example: when we click the food list, a description of the food that is clicked will appear or when we click the data on another page, it is more detailed than the data on the list
The following is the code from the start page :
var notifikasiSiswa = [];
//====================== Fungsi ListView ================================
class _NotifikasiSiswaState extends State<NotifikasiSiswa> {
Future<SharedPreferences> _sprefs = SharedPreferences.getInstance();
int id;
#override
Future<Null> getData() async {
final SharedPreferences prefs = await _sprefs;
int data = prefs.getInt('id');
this.setState(() {
id = data ;
});
// counter = data;
// api coba dsni
NotifikasiSiswaMo.getPengumuman(id.toString()).then((value) {
notifikasiSiswa = value;
this.setState(() {
notifikasiSiswa = value;
});
print("muncul");
});
// print(token);
}
#override
void initState() {
super.initState();
setState(() {
getData();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Materi Siswa"),),
body: ListView.builder( // Auto Scroll Jika Data yang dimunculkan banyak
reverse: true,
itemCount: notifikasiSiswa.length,
itemBuilder: (context, index){
return Center(
child: Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 15.0 ),
child: Stack(
children: <Widget>[
Container(
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_borderRadius),
gradient: LinearGradient(
colors: [Colors.white, Colors.white],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
)
),
Positioned.fill(child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return NotifikasiSiswaDetail(
s_date : notifikasiSiswa[index].date,
s_title : notifikasiSiswa[index].title,
s_message :notifikasiSiswa[index].message
);
},
));
},
child: Row(
children: <Widget>[
Expanded(
child:
Center(
child: Icon(Icons.notifications_active))
),
Expanded(
flex: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, //Vertical dengan posisi aligmn start
children: <Widget>[
Text(notifikasiSiswa[index].date ?? "00/00/00",
style: TextStyle(
color: Colors.black45,
fontWeight: FontWeight.w500),),
SizedBox(
height: 6.0,
),
how on the next page ?
final List args = ModalRoute.of(context).settings.arguments;Provided that I understood your question correctly, you can pass data to a new page using the arguments parameter in your Navigator.pushNamed().
Navigator.pushNamed(context, '/newPage', arguments: /* data you want to pass */);
To access this data from the new page you can use this line inside its build method.
final args = ModalRoute.of(context).settings.arguments;
You can learn more about this here: https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
You have (at least) two options here:
Save the data in the state. If the data is not so big and complex, you can carry it around via state.
Make a new request. If you need a lot more details than just title and description such as comments, like, shares, etc. then you better make a new request. Yes, it costs you more in terms of bandwidth and also money, but it gives you more room to work with.

How to fade in/out a widget from SliverAppBar while scrolling?

I want to 'fade in' and 'fade out' a widget from SliverAppBar when user scrolls on the screen.
This is an example of what I want to do:
Here is my code without 'fading':
https://gist.github.com/nesscx/721cd823350848e3d594ba95df68a7fa
import 'package:flutter/material.dart';
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fading out CircleAvatar',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: new SliverAppBar(
expandedHeight: 254.0,
pinned: false,
leading: Icon(Icons.arrow_back),
title:Text('Fade'),
forceElevated: innerBoxIsScrolled,
flexibleSpace: new FlexibleSpaceBar(
centerTitle: true,
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
CircleAvatar(
radius: 36.0,
child: Text(
'N',
style: TextStyle(
color: Colors.white,
),
),
backgroundColor: Colors.green,
),
Text('My Name'),
],
),
background: Container(
color: Colors.purple,
),
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
new TabBar(
indicatorColor: Colors.white,
indicatorWeight: 3.0,
tabs: <Tab>[
Tab(text: 'TAB 1',),
Tab(text: 'TAB 2',),
],
),
),
),
];
},
body: TabBarView(
children: <Widget>[
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 1', style: TextStyle(color: Colors.black, fontSize: 80.0)),
),
),
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 2', style: TextStyle(color: Colors.red, fontSize: 80.0)),
),
),
],
),
),
),
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.deepPurple,
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
This is actually quite simple using ScrollController and the Opacity Widget. Here's a basic example:
https://gist.github.com/smkhalsa/ec33ec61993f29865a52a40fff4b81a2
This solution uses the bloc pattern with a StreamBuilder, in addition to a LayoutBuilder to get a measure of the height available for the first time flutter builds the widget. The solution is probably not perfect as a locking semaphore was needed to prevent flutter constantly rebuild the widget in the StreamBuilder. The solution does not rely on animations, so you can stop the swipe midway and have a partially visible AppBar and CircleAvatar & Text.
Initially, I attempted to create this effect with setState, that did not work since the state became dirty because the build was not finished when setState was called before LayoutBuilder's return statement.
I have separated the solution into three files. The first main.dart resembles mostly what nesscx posted, with changes making the widget stateful and using a custom widget which is shown in the second file.
import 'package:flutter/material.dart';
import 'flexible_header.dart'; // The code in the next listing
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fading out CircleAvatar',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home: App());
}
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// A locking semaphore, it prevents unnecessary continuous updates of the
// bloc state when the user is not engaging with the app.
bool allowBlocStateUpdates = false;
allowBlocUpdates(bool allow) => setState(() => allowBlocStateUpdates = allow);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Listener(
// Only to prevent unnecessary state updates to the FlexibleHeader's bloc.
onPointerMove: (details) => allowBlocUpdates(true),
onPointerUp: (details) => allowBlocUpdates(false),
child: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
// Custom widget responsible for the effect
FlexibleHeader(
allowBlocStateUpdates: allowBlocStateUpdates,
innerBoxIsScrolled: innerBoxIsScrolled,
),
SliverPersistentHeader(
pinned: true,
delegate: _SliverAppBarDelegate(
new TabBar(
indicatorColor: Colors.white,
indicatorWeight: 3.0,
tabs: <Tab>[
Tab(text: 'TAB 1'),
Tab(text: 'TAB 2'),
],
),
),
),
];
},
body: TabBarView(
children: <Widget>[
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 1',
style: TextStyle(color: Colors.black, fontSize: 80.0)),
),
),
SingleChildScrollView(
child: Container(
height: 300.0,
child: Text('Test 2',
style: TextStyle(color: Colors.red, fontSize: 80.0)),
),
),
],
),
),
),
),
);
}
}
// Not modified
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(
color: Colors.deepPurple,
child: _tabBar,
);
}
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return false;
}
}
The second file flexible_header.dart contains the StreamBuilder and the LayoutBuilder, which closely interact with the bloc to update the UI with new opacity values. New height values are passed to the bloc which in turn updates the opacity.
import 'package:flutter/material.dart';
import 'bloc.dart'; // The code in the next listing
/// Creates a SliverAppBar that gradually toggles (with opacity) between
/// showing the widget in the flexible space, and the SliverAppBar's title and leading.
class FlexibleHeader extends StatefulWidget {
final bool allowBlocStateUpdates;
final bool innerBoxIsScrolled;
const FlexibleHeader(
{Key key, this.allowBlocStateUpdates, this.innerBoxIsScrolled})
: super(key: key);
#override
_FlexibleHeaderState createState() => _FlexibleHeaderState();
}
class _FlexibleHeaderState extends State<FlexibleHeader> {
FlexibleHeaderBloc bloc;
#override
void initState() {
super.initState();
bloc = FlexibleHeaderBloc();
}
#override
void dispose() {
super.dispose();
bloc.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
initialData: bloc.initial(),
stream: bloc.stream,
builder: (BuildContext context, AsyncSnapshot<FlexibleHeaderState> stream) {
FlexibleHeaderState state = stream.data;
// Main widget responsible for the effect
return SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
child: SliverAppBar(
expandedHeight: 254,
pinned: true,
primary: true,
leading: Opacity(
opacity: state.opacityAppBar,
child: Icon(Icons.arrow_back),
),
title: Opacity(
opacity: state.opacityAppBar,
child: Text('Fade'),
),
forceElevated: widget.innerBoxIsScrolled,
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// LayoutBuilder allows us to receive the max height of
// the widget, the first value is stored in the bloc which
// allows later values to easily be compared to it.
//
// Simply put one can easily turn it to a double from 0-1 for
// opacity.
print("BoxConstraint - Max Height: ${constraints.maxHeight}");
if (widget.allowBlocStateUpdates) {
bloc.update(state, constraints.maxHeight);
}
return Opacity(
opacity: state.opacityFlexible,
child: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
centerTitle: true,
title: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// Remove flexible for constant width of the
// CircleAvatar, but only if you want to introduce a
// RenderFlex overflow error for the text, but it is
// only visible when opacity is very low.
Flexible(
child: CircleAvatar(
radius: 36.0,
child: Text('N',
style: TextStyle(color: Colors.white)),
backgroundColor: Colors.green),
),
Flexible(child: Text('My Name')),
],
),
background: Container(color: Colors.purple),
),
);
},
)),
);
},
);
}
}
The third file is a bloc, bloc.dart. To obtain the opacity effect some math had to be done, and checking that the opacity value was between 0 to 1, the solution is not perfect, but it works.
import 'dart:async';
/// The variables necessary for proper functionality in the FlexibleHeader
class FlexibleHeaderState{
double initialHeight;
double currentHeight;
double opacityFlexible = 1;
double opacityAppBar = 0;
FlexibleHeaderState();
}
/// Used in a StreamBuilder to provide business logic with how the opacity is updated.
/// depending on changes to the height initially
/// available when flutter builds the widget the first time.
class FlexibleHeaderBloc{
StreamController<FlexibleHeaderState> controller = StreamController<FlexibleHeaderState>();
Sink get sink => controller.sink;
Stream<FlexibleHeaderState> get stream => controller.stream;
FlexibleHeaderBloc();
_updateOpacity(FlexibleHeaderState state) {
if (state.initialHeight == null || state.currentHeight == null){
state.opacityFlexible = 1;
state.opacityAppBar = 0;
} else {
double offset = (1 / 3) * state.initialHeight;
double opacity = (state.currentHeight - offset) / (state.initialHeight - offset);
//Lines below prevents exceptions
opacity <= 1 ? opacity = opacity : opacity = 1;
opacity >= 0 ? opacity = opacity : opacity = 0;
state.opacityFlexible = opacity;
state.opacityAppBar = (1-opacity).abs(); // Inverse the opacity
}
}
update(FlexibleHeaderState state, double currentHeight){
state.initialHeight ??= currentHeight;
state.currentHeight = currentHeight;
_updateOpacity(state);
_update(state);
}
FlexibleHeaderState initial(){
return FlexibleHeaderState();
}
void dispose(){
controller.close();
}
void _update(FlexibleHeaderState state){
sink.add(state);
}
}
Hope this helps somebody :)

Resources