Related
My build was working fine until I added an image to the launch screen, when I started getting a PhaseScript error. I had to completely delete the ios folder, recreate it, and then make the necessary manual changes again. Now when I run the build on a simulator through android studio, I get this error once the app starts:
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: Bad state: No element
#0 List.first (dart:core-patch/growable_array.dart:339:5)
#1 main (package:globe_flutter/main.dart:19:25)
<asynchronous suspension>
Nothing shows up on the screen. I use a tabBar in the main.dart and a tabBarView to show the different screens. Here is the main.dart:
var firstCamera;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
firstCamera = cameras.first;
await Firebase.initializeApp();
runApp(MyApp());
}
// void main() async {
// WidgetsFlutterBinding.ensureInitialized();
// await Firebase.initializeApp();
// runApp(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: 'GLOBE',
debugShowCheckedModeBanner: false,
home: const MyHome(),
routes: {
LoginScreen.id: (context) => LoginScreen(),
SignupScreen.id: (context) => SignupScreen(),
HomeScreen.id: (context) => HomeScreen()
},
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
#override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin {
late TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
children: [
HomeScreen(),
HomeScreen(),
TakePictureScreen(camera: firstCamera),
HomeScreen(),
ProfileScreen(username: "cjmofficial")
],
controller: _tabController
),
extendBody: true,
bottomNavigationBar: Container(
color: Colors.transparent,
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(50.0),
child: Container(
height: 55,
color: Colors.grey[200],
child: TabBar(
labelColor: Colors.teal[200],
unselectedLabelColor: Colors.blueGrey,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.teal),
insets: EdgeInsets.fromLTRB(50, 0, 50, 40)
),
indicatorColor: Colors.teal,
tabs: [
Tab(icon: Icon(Icons.home_outlined)),
Tab(icon: Icon(Icons.explore_outlined)),
Tab(icon: Icon(Icons.camera_alt_outlined)),
Tab(icon: Icon(Icons.movie_outlined)),
Tab(icon: Icon(Icons.person_outline))
],
controller: _tabController),
),
),
),
);
}
}
and the first screen that is supposed to show in the TabBarView:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
static const String id = "home_screen";
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Color _likeButtonColor = Colors.black;
Widget _buildPost(String username, String imageUrl, String caption, String pfpUrl) {
return Container(
color: Colors.white,
child: Column(
children: [
GestureDetector(
onTap: (){},
child: Container(
height: 50,
color: Colors.deepOrangeAccent[100],
child: Row(
children: [
SizedBox(width: 5),
CircleAvatar(
child: ClipOval(
child: Image.network(
pfpUrl,
height: 40,
width: 40,
),
),
),
SizedBox(width: 10),
Icon(Icons.more_vert, size: 20),
SizedBox(width: 10),
Text(username, style: TextStyle(fontSize: 15))
],
),
),
),
Stack(
children: [
Image.asset("images/post_background.jpg"),
Padding(
padding: const EdgeInsets.all(20.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(imageUrl, fit: BoxFit.cover)),
),
],
),
Container(
height: 100,
child: Column(
children: [
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
setState(() {
HapticFeedback.lightImpact();
});
},
icon: Icon(Icons.thumb_up_alt_outlined, size: 30)),
Text("l", style: TextStyle(fontSize: 30)),
IconButton(
onPressed: () {
setState(() {
HapticFeedback.lightImpact();
GallerySaver.saveImage(imageUrl);
});
},
icon: Icon(Icons.ios_share, size: 30))
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(caption, style: const TextStyle(fontSize: 15))
],
)
],
),
)
],
),
);
}
List<Post> listPosts = [];
fetchPosts() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
result.docs.forEach((res) async {
print(res.id);
QuerySnapshot posts = await userRef.doc(res.id).collection("posts").get();
posts.docs.forEach((res) {
listPosts.add(Post.fromJson(res.data() as Map<String, dynamic>));
});
// Other method
// listPosts = posts.docs.map((doc) => Post.fromJson(doc.data() as Map<String, dynamic>)).toList();
setState(() {});
});
setState(() {
listPosts.sort((a, b) => a.postedDate.compareTo(b.postedDate));
});
}
pfpUrl(String username) async {
// Get pfpUrl
String downloadURL = await FirebaseStorage.instance
.ref(username + "/profile_picture.png")
.getDownloadURL();
return downloadURL;
}
#override
void initState() {
fetchPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: AppBar(
leading:
Icon(Icons.monetization_on_outlined, color: Colors.blueGrey),
centerTitle: true,
title: Text("GLOBE", style: TextStyle(color: Colors.blueGrey)),
actions: [
Icon(Icons.search, color: Colors.blueGrey),
SizedBox(width: 10)
],
backgroundColor: Colors.tealAccent[100]),
),
body: ListView.builder(
itemCount: listPosts.length,
itemBuilder: (BuildContext context, int index) {
// We retrieve the post at index « index »
final post = listPosts[index];
// Get name from id
var parts = post.id.split('_');
var username = parts[0].trim();
// Get pfpUrl
String pfpUrlString = "https://play-lh.googleusercontent.com/IeNJWoKYx1waOhfWF6TiuSiWBLfqLb18lmZYXSgsH1fvb8v1IYiZr5aYWe0Gxu-pVZX3";
return _buildPost(username, post.postUrlString, post.caption, pfpUrlString);
}),
);
}
}
I'm new to flutter and all I can understand about this is that something cannot load. I also ran a build with verbose but it didn't give anymore information. If someone can help me understand the issue, that would be great.
Ios simulators do not have cameras so on line 19, when firstCamera = cameras.first is called, there is no first camera.
Trying to implement showBottomSheet into my app, but it is throwing an error: Scaffold.of() called with a context that does not contain a Scaffold.
After searching the web a bit, I added a GlobalKey but that seems like it is not doing the trick. Does anyone have a suggestion?
class _DashboardState extends State<Dashboard> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
int _bottomNavBarCurrentIndex = 0;
dashboardViews.DashboardView dView = new dashboardViews.DashboardView();
List<Widget> _listOffers = new List<Widget>();
Widget _currentView;
void loadDashboardView() {
_currentView = (_bottomNavBarCurrentIndex == 0
? dView.getOfferView(_listOffers, _getOfferData)
: dView.getOrdersView());
}
_showInfoSheet() {
showBottomSheet(
context: _scaffoldKey.currentContext,
builder: (context) {
return Text('Hello');
});
}
Future _getOfferData() async {
loadDashboardView();
List<Widget> _resultsOffers = new List<Widget>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String _token = prefs.getString('token');
final responseOffers =
await http.get(globals.apiConnString + 'GetActiveOffers?token=$_token');
if (responseOffers.statusCode == 200) {
List data = json.decode(responseOffers.body);
for (var i = 0; i < data.length; i++) {
_resultsOffers.add(GestureDetector(
onTap: () => _showInfoSheet(),
child: Card(
child: Padding(
padding: EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children: <Widget>[
Expanded(
flex: 5,
child: Text('${data[i]['Title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: globals.themeColor4))),
Expanded(
flex: 3,
child: Row(children: <Widget>[
Icon(Icons.access_time,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 10, 0),
child: Text('11:30 PM',
style:
TextStyle(color: Colors.black))),
])),
Expanded(
flex: 1,
child: Row(children: <Widget>[
Icon(Icons.local_dining,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Text('${i.toString()}',
style:
TextStyle(color: Colors.black))),
])),
]),
Padding(padding: EdgeInsets.all(10)),
Row(children: <Widget>[
Text(
'Created May 2, 2019 at 2:31 PM',
style: TextStyle(color: Colors.grey[600]),
textAlign: TextAlign.start,
)
])
])))));
}
}
setState(() {
_listOffers = _resultsOffers;
});
loadDashboardView();
}
}
void _bottomNavBarTap(int index) {
setState(() {
_bottomNavBarCurrentIndex = index;
loadDashboardView();
});
}
void pullRefresh() {
_getOfferData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.grey[200],
body: _currentView,
bottomNavigationBar: BottomNavigationBar(
onTap: _bottomNavBarTap,
currentIndex: _bottomNavBarCurrentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.near_me), title: Text('OFFERS')),
BottomNavigationBarItem(
icon: Icon(Icons.broken_image), title: Text('ORDERS'))
],
),
);
}
}
Hope someone can point me in the right direction, thanks!
EDIT: Here is one of the links I looked at with the same problem, and I tried setting the builder context to c as suggested but didn't work: https://github.com/flutter/flutter/issues/23234
EDIT 2: Adding screenshot that shows the variable contents when I'm getting the Scaffold.of() called with a context that does not contain a Scaffold error.
The reason you're getting this error is because you're calling Scaffold during its build process, and thus your showBottomSheet function can't see it. A workaround this problem is to provide a stateful widget with a Scaffold, assign the Scaffold a key , and then pass it to your Dashboard (I assumed it is the name of your stateful widget from your state class). You don't need to assign a key to the Scaffold inside the build of _DashboardState.
This is the class where you provide a Scaffold with key:
class DashboardPage extends StatefulWidget {
#override
_DashboardPageState createState() => _DashboardPageState() ;
}
class _DashboardPageState extends State<DashboardPage> {
final GlobalKey<ScaffoldState> scaffoldStateKey ;
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldStateKey,
body: Dashboard(scaffoldKey: scaffoldStateKey),
);
}
Your original Dashboard altered :
class Dashboard extends StatefulWidget{
Dashboard({Key key, this.scaffoldKey}):
super(key: key);
final GlobalKey<ScaffoldState> scaffoldKey ;
#override
_DashboardState createState() => new _DashboardState();
}
Your _DashboardState with changes outlined :
class _DashboardState extends State<Dashboard> {
int _bottomNavBarCurrentIndex = 0;
dashboardViews.DashboardView dView = new dashboardViews.DashboardView();
List<Widget> _listOffers = new List<Widget>();
Widget _currentView;
void loadDashboardView() {
_currentView = (_bottomNavBarCurrentIndex == 0
? dView.getOfferView(_listOffers, _getOfferData)
: dView.getOrdersView());
}
_showInfoSheet() {
showBottomSheet(
context: widget.scaffoldKey.currentContext, // referencing the key passed from [DashboardPage] to use its Scaffold.
builder: (context) {
return Text('Hello');
});
}
Future _getOfferData() async {
loadDashboardView();
List<Widget> _resultsOffers = new List<Widget>();
SharedPreferences prefs = await SharedPreferences.getInstance();
String _token = prefs.getString('token');
final responseOffers =
await http.get(globals.apiConnString + 'GetActiveOffers?token=$_token');
if (responseOffers.statusCode == 200) {
List data = json.decode(responseOffers.body);
for (var i = 0; i < data.length; i++) {
_resultsOffers.add(GestureDetector(
onTap: () => _showInfoSheet(),
child: Card(
child: Padding(
padding: EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children: <Widget>[
Expanded(
flex: 5,
child: Text('${data[i]['Title']}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: globals.themeColor4))),
Expanded(
flex: 3,
child: Row(children: <Widget>[
Icon(Icons.access_time,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 10, 0),
child: Text('11:30 PM',
style:
TextStyle(color: Colors.black))),
])),
Expanded(
flex: 1,
child: Row(children: <Widget>[
Icon(Icons.local_dining,
size: 15, color: Colors.grey),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Text('${i.toString()}',
style:
TextStyle(color: Colors.black))),
])),
]),
Padding(padding: EdgeInsets.all(10)),
Row(children: <Widget>[
Text(
'Created May 2, 2019 at 2:31 PM',
style: TextStyle(color: Colors.grey[600]),
textAlign: TextAlign.start,
)
])
])))));
}
}
setState(() {
_listOffers = _resultsOffers;
});
loadDashboardView();
}
void _bottomNavBarTap(int index) {
setState(() {
_bottomNavBarCurrentIndex = index;
loadDashboardView();
});
}
void pullRefresh() {
_getOfferData();
}
#override
Widget build(BuildContext context) {
// Scaffold key has been removed as there is no further need to it.
return Scaffold(
backgroundColor: Colors.grey[200],
body: _currentView,
bottomNavigationBar: BottomNavigationBar(
onTap: _bottomNavBarTap,
currentIndex: _bottomNavBarCurrentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.near_me), title: Text('OFFERS')),
BottomNavigationBarItem(
icon: Icon(Icons.broken_image), title: Text('ORDERS'))
],
),
);
}
}
}
I want to change color of every icon after pressing. But all of icons in a ExpandableContainerchange after pressing one of them.
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
.
.
.
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: ...
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: ...
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor = _iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: ...
),
);
},
itemCount: ...,
))
],
),
);
}
}
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double expandedHeight;
final Widget child;
ExpandableContainer({
#required this.child,
this.expandedHeight,
this.expanded = true,
});
#override
Widget build(BuildContext context) {
.
.
.
}
Whole code:
import 'package:flutter/material.dart';
import 'data.dart';
void main() {
runApp(new MaterialApp(home: new Home()));
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey,
appBar: new AppBar(
title: new Text("Expandable List", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.lightGreen,
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ExpandableListView(title: broadcast[index].title, ind: index);
},
itemCount: broadcast.length,
),
);
}
}
class ExpandableListView extends StatefulWidget {
final String title;
final int ind;
const ExpandableListView({this.title, this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.title,
style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0, color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 * (broadcast[widget.ind].contents.length < 4 ? broadcast[widget.ind].contents.length : 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: new Text(
broadcast[widget.ind].contents[index],
style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor = _iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text ('${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
textAlign: TextAlign.right, style:TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
class ExpandableContainer extends StatelessWidget {
final bool expanded;
final double expandedHeight;
final Widget child;
//final Color iconColor;
ExpandableContainer({
#required this.child,
this.expandedHeight,
this.expanded = true,
//this.iconColor,
});
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return new AnimatedContainer(
duration: new Duration(milliseconds: 100),
curve: Curves.easeInOut,
width: screenWidth,
height: expanded ? expandedHeight : 0.0,
child: new Container(
child: child,
decoration: new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.blue)),
),
);
}
}
You need to make the list item a StatefulWidget in which you have the state _iconColor
Stateful List Tile
class StatefulListTile extends StatefulWidget {
const StatefulListTile({this.subtitle, this.title});
final String subtitle, title;
#override
_StatefulListTileState createState() => _StatefulListTileState();
}
class _StatefulListTileState extends State<StatefulListTile> {
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(
border: new Border.all(width: 1.0, color: Colors.grey),
color: Colors.black),
child: new ListTile(
title: new Text(
widget?.title ?? "",
style: new TextStyle(
fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: _iconColor),
onPressed: () {
setState(() {
_iconColor =
_iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text(widget?.subtitle ?? "",
textAlign: TextAlign.right, style: TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
}
}
Usage
class ExpandableListView extends StatefulWidget {
final String title;
final int ind;
const ExpandableListView({this.title, this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
Color _iconColor = Colors.white;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.title,
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 *
(broadcast[widget.ind].contents.length < 4
? broadcast[widget.ind].contents.length
: 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return StatefulListTile(
title: broadcast[widget.ind].contents[index],
subtitle:
'${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
You should make the color property distinct for each element in the ListView, what you are doing is that the color is global and shared among all the icons in the ListView, for this reason all icons are changing their color when one icon is pressed.
class Broadcast {
final String title;
List<String> contents;
List<String> team = [];
List<String> time = [];
List<String> channel = [];
Color iconColor = Colors.white; //initialize at the beginning
Broadcast(this.title, this.contents, this.team, this.time, this.channel); //, this.icon);
}
edit your ExpandableListView
class ExpandableListView extends StatefulWidget {
final int ind;
final Broadcast broadcast;
const ExpandableListView({this.broadcast,this.ind});
#override
_ExpandableListViewState createState() => new _ExpandableListViewState();
}
edit your Home class
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey,
appBar: new AppBar(
title: new Text("Expandable List", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.lightGreen,
),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new ExpandableListView(title: broadcast[index], ind: index);
},
itemCount: broadcast.length,
),
);
}
}
edit your _ExpandableListViewState
class _ExpandableListViewState extends State<ExpandableListView> {
bool expandFlag = false;
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 1.0),
child: new Column(
children: <Widget>[
new Container(
color: Colors.blue[300],
padding: new EdgeInsets.symmetric(horizontal: 5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new IconButton(
icon: new Container(
height: 50.0,
width: 50.0,
decoration: new BoxDecoration(
color: Colors.deepOrange,
shape: BoxShape.circle,
),
child: new Center(
child: new Icon(
expandFlag ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Colors.white,
size: 30.0,
),
),
),
onPressed: () {
setState(() {
expandFlag = !expandFlag;
});
}),
new Text(
widget.broadcast.title,
style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0, color: Colors.black87),
)
],
),
),
new ExpandableContainer(
expanded: expandFlag,
expandedHeight: 90.0 * (broadcast[widget.ind].contents.length < 4 ? broadcast[widget.ind].contents.length : 4), // + (0.0 ?: 29.0),
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Container(
decoration:
new BoxDecoration(border: new Border.all(width: 1.0, color: Colors.grey), color: Colors.black),
child: new ListTile(
title: new Text(
broadcast[widget.ind].contents[index],
style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.lightGreen),
textAlign: TextAlign.right,
),
leading: new IconButton(
icon: Icon(Icons.star, color: widget.broadcast.iconColor),
onPressed: () {
setState(() {
widget.broadcast.iconColor = widget.broadcast.iconColor == Colors.white ? Colors.yellow : Colors.white;
});
},
),
subtitle: new Text ('${broadcast[widget.ind].team[index]}\n${broadcast[widget.ind].time[index]} ${broadcast[widget.ind].channel[index]}',
textAlign: TextAlign.right, style:TextStyle(color: Colors.white)),
isThreeLine: true,
),
);
},
itemCount: broadcast[widget.ind].contents.length,
))
],
),
);
}
}
I have this piece of code which I got from Style clipboard in flutter
showMenu(
context: context,
// TODO: Position dynamically based on cursor or textfield
position: RelativeRect.fromLTRB(0.0, 600.0, 300.0, 0.0),
items: [
PopupMenuItem(
child: Row(
children: <Widget>[
// TODO: Dynamic items / handle click
PopupMenuItem(
child: Text(
"Paste",
style: Theme.of(context)
.textTheme
.body2
.copyWith(color: Colors.red),
),
),
PopupMenuItem(
child: Text("Select All"),
),
],
),
),
],
);
This code works great, except that the popup that is created is at a fixed position, how would I make it so that it pops up at the mouse/press/finger/cursor position or somewhere near that, kind of like when you want to copy and paste on your phone. (This dialog popup will not be used for copy and pasting)
I was able to solve a similar issue by using this answer:
https://stackoverflow.com/a/54714628/559525
Basically, I added a GestureDetector() around each ListTile and then you use onTapDown to store your press location and onLongPress to call your showMenu function. Here are the critical functions I added:
_showPopupMenu() async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
await showMenu(
context: context,
position: RelativeRect.fromRect(
_tapPosition & Size(40, 40), // smaller rect, the touch area
Offset.zero & overlay.size // Bigger rect, the entire screen
),
items: [
PopupMenuItem(
child: Text("Show Usage"),
),
PopupMenuItem(
child: Text("Delete"),
),
],
elevation: 8.0,
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
And then here is the full code (you'll have to tweak a few things like the image, and filling in the list of devices):
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'dart:core';
class RecentsPage extends StatefulWidget {
RecentsPage({Key key, this.title}) : super(key: key);
final String title;
#override
_RecentsPageState createState() => _RecentsPageState();
}
class _RecentsPageState extends State<RecentsPage> {
List<String> _recents;
var _tapPosition;
#override
void initState() {
super.initState();
_tapPosition = Offset(0.0, 0.0);
getRecents().then((value) {
setState(() {
_recents = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(height: 25),
Stack(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 40),
child: Center(
child: AutoSizeText(
"Recents",
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 32),
),
),
),
Container(
padding: EdgeInsets.only(left: 30, top: 0),
child: GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Transform.scale(
scale: 2.0,
child: Icon(
Icons.chevron_left,
),
)),
),
],
),
Container(
height: 15,
),
Container(
height: 2,
color: Colors.blue,
),
Container(
height: 10,
),
Flexible(
child: ListView(
padding: EdgeInsets.all(15.0),
children: ListTile.divideTiles(
context: context,
tiles: _getRecentTiles(),
).toList(),
),
),
Container(height: 15),
],
),
),
),
);
}
List<Widget> _getRecentTiles() {
List<Widget> devices = List<Widget>();
String _dev;
String _owner = "John Doe";
if (_recents != null) {
for (_dev in _recents.reversed) {
if (_dev != null) {
_dev = _dev.toUpperCase().trim();
String serial = "12341234";
devices.add(GestureDetector(
onTapDown: _storePosition,
onLongPress: () {
print("long press of $serial");
_showPopupMenu();
},
child: ListTile(
contentPadding: EdgeInsets.symmetric(vertical: 20),
leading: Transform.scale(
scale: 0.8,
child: Image(
image: _myImage,
)),
title: AutoSizeText(
"$_owner",
maxLines: 1,
style: TextStyle(fontSize: 22),
),
subtitle: Text("Serial #: $serial"),
trailing: Icon(Icons.keyboard_arrow_right),
)));
}
}
} else {
devices.add(ListTile(
contentPadding: EdgeInsets.symmetric(vertical: 20),
title: AutoSizeText(
"No Recent Devices",
maxLines: 1,
style: TextStyle(fontSize: 20),
),
subtitle:
Text("Click the button to add a device"),
onTap: () {
print('add device');
},
));
}
return devices;
}
_showPopupMenu() async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
await showMenu(
context: context,
position: RelativeRect.fromRect(
_tapPosition & Size(40, 40), // smaller rect, the touch area
Offset.zero & overlay.size // Bigger rect, the entire screen
),
items: [
PopupMenuItem(
child: Text("Show Usage"),
),
PopupMenuItem(
child: Text("Delete"),
),
],
elevation: 8.0,
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
Use gesture detector's onTapDown like this
GestureDetector(
onTapDown: (TapDownDetails details) {
showPopUpMenu(details.globalPosition);
},
then in this method we use tap down details to find position
Future<void> showPopUpMenu(Offset globalPosition) async {
double left = globalPosition.dx;
double top = globalPosition.dy;
await showMenu(
color: Colors.white,
//add your color
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
PopupMenuItem(
value: 1,
child: Padding(
padding: const EdgeInsets.only(left: 0, right: 40),
child: Row(
children: [
Icon(Icons.mail_outline),
SizedBox(
width: 10,
),
Text(
"Menu 1",
style: TextStyle(color: Colors.black),
),
],
),
),
),
PopupMenuItem(
value: 2,
child: Padding(
padding: const EdgeInsets.only(left: 0, right: 40),
child: Row(
children: [
Icon(Icons.vpn_key),
SizedBox(
width: 10,
),
Text(
"Menu 2",
style: TextStyle(color: Colors.black),
),
],
),
),
),
PopupMenuItem(
value: 3,
child: Row(
children: [
Icon(Icons.power_settings_new_sharp),
SizedBox(
width: 10,
),
Text(
"Menu 3",
style: TextStyle(color: Colors.black),
),
],
),
),
],
elevation: 8.0,
).then((value) {
print(value);
if (value == 1) {
//do your task here for menu 1
}
if (value == 2) {
//do your task here for menu 2
}
if (value == 3) {
//do your task here for menu 3
}
});
hope it works
Here is a reusable widget which does what you need. Just wrap your Text or other Widget with this CopyableWidget and pass in the onGetCopyTextRequested. It will display the Copy menu when the widget is long pressed, copy the text contents returned to the clipboard, and display a Snackbar on completion.
/// The text to copy to the clipboard should be returned or null if nothing can be copied
typedef GetCopyTextCallback = String Function();
class CopyableWidget extends StatefulWidget {
final Widget child;
final GetCopyTextCallback onGetCopyTextRequested;
const CopyableWidget({
Key key,
#required this.child,
#required this.onGetCopyTextRequested,
}) : super(key: key);
#override
_CopyableWidgetState createState() => _CopyableWidgetState();
}
class _CopyableWidgetState extends State<CopyableWidget> {
Offset _longPressStartPos;
#override
Widget build(BuildContext context) {
return InkWell(
highlightColor: Colors.transparent,
onTapDown: _onTapDown,
onLongPress: () => _onLongPress(context),
child: widget.child
);
}
void _onTapDown(TapDownDetails details) {
setState(() {
_longPressStartPos = details?.globalPosition;
});
}
void _onLongPress(BuildContext context) async {
if (_longPressStartPos == null)
return;
var isCopyPressed = await showCopyMenu(
context: context,
pressedPosition: _longPressStartPos
);
if (isCopyPressed == true && widget.onGetCopyTextRequested != null) {
var copyText = widget.onGetCopyTextRequested();
if (copyText != null) {
await Clipboard.setData(ClipboardData(text: copyText));
_showSuccessSnackbar(
context: context,
text: "Copied to the clipboard"
);
}
}
}
void _showSuccessSnackbar({
#required BuildContext context,
#required String text
}) {
var scaffold = Scaffold.of(context, nullOk: true);
if (scaffold != null) {
scaffold.showSnackBar(
SnackBar(
content: Row(
children: <Widget>[
Icon(
Icons.check_circle_outline,
size: 24,
),
SizedBox(width: 8),
Expanded(
child: Text(text)
)
],
)
)
);
}
}
}
Future<bool> showCopyMenu({
BuildContext context,
Offset pressedPosition
}) {
var x = pressedPosition.dx;
var y = pressedPosition.dy;
return showMenu<bool>(
context: context,
position: RelativeRect.fromLTRB(x, y, x + 1, y + 1),
items: [
PopupMenuItem<bool>(value: true, child: Text("Copy")),
]
);
}
class _DaftarMuridState extends State<DaftarMurid> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
new Flexible(
child: new FirebaseAnimatedList(//new
query: db.reference().child("Murid"),
sort: (a, b) => a.key.compareTo(b.key),
padding: new EdgeInsets.all(8.0),
itemBuilder: (_, DataSnapshot dataSnapshot, Animation<double> animations,x){
return new DaftarMuridView(
snapshot: dataSnapshot,
animation: animations,
);//new
}
),
),
],
),
);
}
}
class DaftarMuridViewState extends State<DaftarMuridView>{
DaftarMuridViewState({this.snapshot, this.animation});
final DataSnapshot snapshot;
final Animation animation;
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
String fotoUrl = snapshot.value['Foto'];
String ig = snapshot.value['Instagram'];
hash.putIfAbsent(snapshot.value['Nama'], () => false);
bool expanded = hash[snapshot.value['Nama']];
var expansionPanel = new ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
hash.remove(snapshot.value['Nama']);
hash.putIfAbsent(snapshot.value['Nama'], () => !isExpanded);
expanded = !expanded;
});
},
children: [new ExpansionPanel(headerBuilder: (BuildContext context, bool isExpanded) {
return new ListTile(
leading: const Icon(Icons.school),
title: new Text(
snapshot.value['Nama'],
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
children: <Widget>[
new CachedNetworkImage(
imageUrl: fotoUrl == null?"https://drive.google.com/uc?export=download&id=1tkqO59S9jiWpkzHQNJRKLuCGYIn5kK_v":fotoUrl,
placeholder: new CircularProgressIndicator(),
errorWidget: new CachedNetworkImage(imageUrl: "https://drive.google.com/uc?export=download&id=1tkqO59S9jiWpkzHQNJRKLuCGYIn5kK_v"),
fadeOutDuration: new Duration(seconds: 1),
fadeInDuration: new Duration(seconds: 1),
height: size.height / 2.0,
width: size.width / 2.0,
alignment: Alignment.center,
),
new ListTile(
leading: const Icon(Icons.today),
title: const Text('Tanggal Lahir'),
subtitle: new Text(snapshot.value['Tanggal Lahir']),
),
new Row(
children: <Widget>[
ig != null ?
new FlatButton(
onPressed: () => _instagram(ig),
child: new CachedNetworkImage(imageUrl: "http://diylogodesigns.com/blog/wp-content/uploads/2016/05/Instagram-logo-png-icon.png", width: size.width / 4.0, height: size.height / 4.0, ),
)
: new Container(),
],
),
],
),
isExpanded: expanded)],
);
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: animation, curve: Curves.easeOut),
axisAlignment: 0.0,
child: expansionPanel,
);
}
}
is my code not efficient? the process is Get Data from Firebase -> Store it to list view
it's a bit lag when open the activity, maybe because getting the data. But is there a solution for make it doesn't lag?
I cut some code that isn't important.
Use Futures to perform time consuming operations, so it will not freeze the UI.
https://www.dartlang.org/tutorials/language/futures