Pass On Get Results from one class to another - Flutter - flutter

The data unLockCard is properly created in the main class where the button is placed.
When I moved the button to a dialog in a different class - the unLockCard is lost. I receive the error message
What is the best way to pass on unLockCard[number] = tarots[0]; into a different widget or class.
Homepage
List<bool> flips = [false, false, false, false];
List tarots = [];
List unLockCard = [];
Widget _buildTarotCard(key, number, title) {
return Column(
children: [
FlipCard(
key: key,
flipOnTouch: true,
front: GestureDetector(
onTap: () {
tarots.shuffle();
key.currentState.toggleCard();
setState(() {
flips[number] = true;
});
unLockCard[number] = tarots[0];
tarots.removeAt(0);
},
Dialog
Widget _showDialog(BuildContext context) {
Future.delayed(Duration.zero, () => showAlert(context));
return Container(
color: Color(0xFF2C3D50),
);
}
void showAlert(BuildContext context) {
List unLockCard = [];
Dialogs.materialDialog(
color: colorTitle,
msg: 'Congratulations, you won 500 points',
msgStyle: TextStyle(color: Colors.white),
title: 'Congratulations',
titleStyle: TextStyle(color: Colors.white),
lottieBuilder: Lottie.asset('assets/lottie/spirituality.json',
fit: BoxFit.contain,
),
dialogWidth: kIsWeb ? 0.3 : null,
context: context,
actions: [
NeumorphicButton(
onPressed: () => Get.toNamed(Routes.DETAILS,
arguments: unLockCard.sublist(0, 4)),
margin: EdgeInsets.symmetric(horizontal: 8.0),

The code is a bit cluttered. You are building a new List unLockCard = []; in the showAlert() method even though you have one already in the HomePage. I suggest you create a CardController class that deals with everything card related and use it in all the views you need. One very elegant way I found is by using the GetX library (https://github.com/jonataslaw/getx). You can declutter your code using a GetView and a GetController (https://github.com/jonataslaw/getx#getview). However, if you don't want to add a new library to your project, you can achieve the same results by having a single point that deals with card actions (i.e. holds a single instance of the unlockCard list and updates it accordingly).

Related

How to add the selected element to the page?

I have a list of news that comes from the API on the screen.
Output code for one card with news(I shortened the code and left only the necessary):
Widget customListTile(Article article, BuildContext context) {
final newsController = Get.put(FavoritesController());
return InkWell(
Container(
child:Row(
textDirection: TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
article.source.name,
style: TextStyle(
color: Colors.white,
backgroundColor: Colors.red,
),
),
IconButton(
onPressed: () {
newsController.addNews(article);
},
icon: const Icon(Icons.favorite_border)),
]
)
),
);
}
There is an icon, when clicked, the addNews() function is called.And there is a controller code where there is this function:
class FavoritesController extends GetxController {
var _news = {}.obs;
void addNews(Article article) {
if(_news.containsKey(article)) {
}
}
get news => _news;
}
I need when clicking on the icon, the news was added to a separate Saved page.
How to write a function for adding news in a controller? And did I write the code correctly? Can you please tell me how this can be implemented?
First of all, you either use
.obs
or
GetxController function update()
There is no need to change your code tho because this will work as well but you're not using GetxController correctly in this case.
Let's focus on the .obs
Move the
Now, make a ListView that is wrapped with Obx(() => ...) which uses the news obs.
Obx(() {
return ListView(
children: Get.find<FavoritesController>().news.map((e) => customListTile(e)).toList(),
);
});
Let's move to the addNews function.
When you add an article use the update function.
if(_news.containsKey(article)) {
_news.update((e) => e[key]=value);
}
Also, move
final newsController = Get.put(FavoritesController());
outside of this function, even though is not necessary, it makes no sense for it to be there.

Is there a way to update BottomNavigationBarItems?

I am new flutter and been trying to work with the BottomNavigationBar. Thing is, i made the bar as i required but i need it to update its items when a Switch is set to true but can't manage to find a work around.
I have two List<BottomNavigationBarItems> that have the two different navBar items which i assign to a third List that contain the active one depending on the switch state. This variable is the one setting the items in my navbarItem but a setState() doesn't seems to re build the navBar.
Is there a way to update the items or do i have to make my own kind of navBar with other widgets ?
Non Ready Items
Ready Items
List<BottomNavigationBarItem> nonReadyBottomItems = [
//some items
];
List<BottomNavigationBarItem> readyBottomItems = [
some other items
];
List<BottomNavigationBarItem> = nonReadyBottomItems;
Scaffold(
body: Center(
child: Switch(
value: switchConnect,
onChanged: (bool boolean) {
setState(() {
switchConnect = boolean;
});
}),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int i) {
setState(() {
pageIndex = i;
if (switchConnect) {
activeItems = readyBottomItems;
} else if (!switchConnect) {
activeItems = nonReadyBottomItems;
}
});
},
currentIndex: pageIndex,
type: BottomNavigationBarType.fixed,
items: activeItems,
),
);
Yes, You create state full Bottom Navigation Bar for change the state of widgets.
Open bottom sheet
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return ModalBottomSheet(
);
});
})
Stateful bottom sheet
class ModalBottomSheet extends StatefulWidget {
#override
_ModalBottomSheetState createState() => _ModalBottomSheetState();
}
class _ModalBottomSheetState extends State<ModalBottomSheet>
{
#override
Widget build(BuildContext context) {
// TODO: implement build
return Wrap(
children: <Widget>[
Container(
margin:
EdgeInsets.only(left: 10.0, right: 10.0, top: 15.0, bottom: 15.0),
child: Column(
Widgets(),
)
)
],
);
}
}
I actually found out why the setState() wasn't working. Seems like the there was some problem with the assignment of the activeItems variable so it wasn't changing the bar since there was nothing new.
So setState actually work on a BottomNavBar !

Flutter State using ChangeNotifier doesn't maintain State

TLDR, I'm building a Stock Screener and when I access a table from one widget it renders proper state, but when called from within the class the state defaults to initial values (I think).
Overall design concept is user configures their stock screener and hits "Screen" button at bottom of page.
Here's a View Example. This is the Market Cap Selector Widget that uses Consumer to retrieve state from the class ScreenerController extends ChangeNotifier
class CapSelectorWidget extends StatelessWidget {
Widget capButton(capSelection) {
return Consumer<ScreenerController>(builder: (context, capData, child) {
return GestureDetector(
child: Container(
padding: EdgeInsets.all(12.0),
margin: EdgeInsets.all(6.0),
decoration: BoxDecoration(
color: capData.getColor(capSelection),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
capSelection,
textAlign: TextAlign.center,
),
),
onTap: () {
capData.toggleCapSelection(capSelection);
},
);
});
}
Both methods getColor and toggleCapSelection ultimately reference and/or update the below _capColorList. Here is the code for the _capColorList and toggleCapSelection
class ScreenerController extends ChangeNotifier {
//////////////////////
// Market Cap Widget
//////////////////////
Map _capColorList = {
'Micro Cap': {
'isSelected': true,
'selectedColor': Colors.teal[300],
'unSelectedColor': Colors.white38,
},
'Small Cap': {
'isSelected': true,
'selectedColor': Colors.teal[400],
'unSelectedColor': Colors.white38,
},
'Mid Cap': {
'isSelected': true,
'selectedColor': Colors.teal[500],
'unSelectedColor': Colors.white38,
},
'Large Cap': {
'isSelected': true,
'selectedColor': Colors.teal[600],
'unSelectedColor': Colors.white38,
},
};
void toggleCapSelection(String capType) {
// Flip bool selected value
_capColorList[capType]['isSelected'] =
!_capColorList[capType]['isSelected'];
notifyListeners();
print(capType + ' ' + _capColorList[capType]['isSelected'].toString());
};
etc...
So far so good. When user hit's a Cap Bucket, it'll toggle state and the color will update.
Now when user hits "Screen" (ie Submit) at bottom of page, this code is implemented.
child: FlatButton(
child: Text('Screen', textAlign: TextAlign.center),
onPressed: () {
ScreenerController().screenStocks();
//Todo: Figure out why Provider.of doesn't work on Screen Button
//Provider.of<ScreenerController>(context, listen: false)
// .screenStocks();
},
),
I implement the following code to create a list of the Market Cap Buckets into a List.
My hypothesis is that because Provider.of isn't working for the FlatButton (it's in the same ChangeNotifierProvider tree), that I'm inadvertently creating a new instance.
Map screenerQueryBuilder() {
//If Cap Button is selected, add it to selectedCaps
List<String> selectedCaps = [];
for (String cap in _capColorList.keys) {
print(cap + _capColorList[cap]['isSelected'].toString());
if (_capColorList[cap]['isSelected']) {
selectedCaps.add(cap);
}
}
print(selectedCaps);
The problem is, even if let's say the Buttons "Mid Cap" and "Micro Cap" are not selected when the user hits Screen, when I iterate over _capColorList.keys the values are always true, and therefore they are always added to the selectedCaps List.
Might anyone know why this is?
Thank you in advance!

SetState in Flutter

I have dynamically created a list view by getting data from an API and putting it into a list of objects, however, when I open the screen, which is opened by pressing a button on another screen, the only way to get the ListView to show on screen is to do a hot refresh. I've tried setting the state in the init_state function but it doesn't seem to be working.
Thanks for any help :)
class _PlanetListState extends State<PlanetList> {
List<Planet> planets = [];
String urlWeb = 'https://swapi.dev/api/planets/';
Future getData(String url) async {
var response = await http.get(url);
if (response.statusCode == 200) {
var data = json.decode(response.body);
Planet obj = Planet(
name: data['name'],
rotationPeriod: data['rotation_period'],
orbitalPeriod: data['orbital_period'],
diameter: data['diameter'],
climate: data['climate'],
gravity: data['gravity'],
terrain: data['terrain'],
population: data['population']
);
planets.add(obj);
print(planets);
}
}
#override
void initState() {
for (var i=0;i<62;i++){
getData('$urlWeb$i/');
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Planets"),
backgroundColor: Colors.amber[800],
),
body: Container(
color: Colors.black,
child: ListView.builder(
itemCount: planets.length,
itemBuilder: (BuildContext context, int index){
return Card(
color: Colors.amber[600],
margin:EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: getColor(planets[index].climate),
backgroundImage: AssetImage('assets/planet.png'),
),
contentPadding: EdgeInsets.all(5),
title: Text('$index: ${planets[index].name}'),
subtitle:Text(planets[index].climate),
),
);
},
),
),
);
}
}
What's happening is that your screen is loading before your data are added to the list, this is because your getData function is async. So when your screen first loads the list is empty, and as you may know in order to show changes on the screen you must do a setState(), this is why when you hot refresh the changes appear.
Now you have multiple options here, first you can take a look at the FutureBuilder, this is a widget that would build twice, once before your Future is resolved and once afterwards, meaning it "auto refreshes". In this case your list of data would become the Future type and your ListView would be wrapped with a FutureBuilder so it's built after the data are ready.
Another option would be to do a setState() every time you add an item to the List of objects you have.

Check / Uncheck ListTiles in ListView.builder() - Flutter

I have a ListView.builder(); in showModalBottomSheet();
Need to select / deselect multiple items on tap everything is well but need to close the modal and show it again to apply changes, another thing is the ListTiles sometimes duplicated more than once, function emptyList doesn't work well.
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'book_details.dart' show BookDetails;
class Explore extends StatefulWidget {
#override
_ExploreState createState() => _ExploreState();
}
var _books,
_categories,
_arranges,
_currentCategory,
_selected,
_primeColor,
_currentFilter,
_isThereIsFilters,
_booksContainer,
_booksWithFilters,
_isLoading,
_noBooks,
_itemIcon;
final GlobalKey<ScaffoldState> _scaffoldKeyExplore =
new GlobalKey<ScaffoldState>();
List<String> _getCats = new List();
List<String> _getArrs = new List();
void _insertCategories() {
for (int i = 0; i < _categories.length; i++) {
_getCats.add(_categories[i]);
}
_getCats.sort();
}
void _insertArranges() {
for (int i = 0; i < _arranges.length; i++) {
_getArrs.add(_arranges[i]);
}
}
class _ExploreState extends State<Explore> with TickerProviderStateMixin {
onCatChange(String category) {
setState(() {
_currentCategory = category;
});
}
#override
void initState() {
super.initState();
_primeColor = Color.fromRGBO(239, 89, 39, 1.0);
_categories = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_arranges = ["أول", "ثاني", "ثالث", "رابع", "خامس"];
_currentFilter = _arranges[0];
_selected = [];
_isThereIsFilters = false;
}
void emptyList(List list) {
for (var i = 0; i < list.length; i++) {
list.remove(list[i]);
}
}
_showSheet(String type) {
switch (type) {
case "filters":
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Expanded(
child: new ListView.builder(
itemCount: _getArrs[0] != null ? _getArrs.length : 0,
itemBuilder: (BuildContext context, int i) {
return new RadioListTile(
title: Text(_getArrs[i]),
value: _getArrs[i],
groupValue: _currentFilter,
onChanged: (val) {
setState(() {
_currentFilter = val;
});
});
}),
)
])),
);
});
break;
case "categories":
default:
showModalBottomSheet(
context: _scaffoldKeyExplore.currentContext,
builder: (BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
child: Column(children: <Widget>[
Container(
color: _primeColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () {
emptyList(_selected);
//Navigator.pop(context);
//_showSheet(type);
}),
IconButton(
icon:
Icon(Icons.done_all, color: Colors.white),
onPressed: () {
if (_selected.length > 0) {
_getFilteredBooks(_selected);
setState(() {
_isThereIsFilters = true;
});
} else {
setState(() {
_isThereIsFilters = false;
});
}
Navigator.pop(context);
})
]),
),
Expanded(
child: new ListView.builder(
itemCount: _getCats != null ? _getCats.length : 0,
itemBuilder: (BuildContext context, int i) {
final _isSelected = _selected.contains(_getCats[i]);
return new ListTile(
leading: Icon(Icons.category),
trailing: _isSelected ? Icon(Icons.done) : null,
title: Text(_getCats[i]),
onTap: () {
setState(() {
_isSelected
? _selected.remove(_getCats[i])
: _selected.add(_getCats[i]);
});
//Navigator.pop(context);
//_showSheet(type);
});
}),
)
])),
);
});
break;
}
}
#override
Widget build(BuildContext context) {
return new Directionality(
textDirection: TextDirection.rtl,
child: new Scaffold(
key: _scaffoldKeyExplore,
appBar:
AppBar(title: Text("استكشاف"), elevation: 0.0, actions: <Widget>[
IconButton(
icon: Icon(Icons.category, color: _primeColor),
onPressed: () => _showSheet("categories")),
IconButton(
icon: Icon(Icons.filter_list, color: _primeColor),
onPressed: () => _showSheet("filters"))
]),
body: Center(child: Text("Nothing..."));
));
}
}
Thank you
need to close the modal and show it again to apply changes
This happens because the showModalBottomSheet's builder needs to be called again to reflect the changes.
In Flutter, StatefulWidgets should be able to rebuild any time the state changes - which is not the case here, because of the bottom sheet being shown.
Why did I run into this issue (on a meta level)?
Storing the state in StatefulWidgets is useful for saving UI state, but you quickly outgrow this technique if you want to store some "app state" or "data state" that is independent of the screen it's on.
It is finally time to fundamentally rethink your state management and settle on a full-fledged state management pattern that decouples the state from the widgets. Luckily, there are a few to choose from:
Making everything global, like you did above. This is generally not a good idea, as you break the contract of setState (state can be modified without the widgets being notified). Also, you break hot restart and stuff like that.
Using an InheritedWidget, where widgets below a root widget can access the same state.
Using a ScopedModel, which builds on top of that.
Using the infamous BLoC pattern, which also builds on top of the InheritedWidget, but adds some Stream-y stuff to make everything more reactive.
Probably many more.
Here is a great Youtube video about state management from Google I/O, where several patterns are being presented.
Anyways, are bottom sheets the right widget for the task ahead?
According to the Material Design spec, the modal bottom sheet is "an alternative to inline menus or simple dialogs on mobile, providing room for additional items, longer descriptions, and iconography".
More concrete, the showModalBottomSheet function is designed to show a widget that doesn't affect the parent over time, but rather - if at all - at a single point in time. That's why it returns a Future<T>, not a Stream<T>.
Be aware that you are trying to use the bottom sheet in a way that it's not intended to be used.
In your case, I'd recommend just using a new screen.