Using flutter 1.20.4
I'm trying to implement a custom digit keyboard (a simple container at the bottom), which should appear into view when tapping on one of the rows in the List.
Here a small snippet of how I do it:
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
However, when the keyboard appears, the bottom rows get obscured by the container.
I tried using Scrollable.ensureVisible, that works for the middle rows, but the last ones are still obscured. It seems like the ScrollView still has it's old size (full height) when Scrollable.ensureVisible() kicks in (I notice this by looking at the ScrollBar).
Code snippet:
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
See video below.
Keyboard obscures last rows when tapped (here clicking on row 14)
However, once the keyboard is up, the SingleChildScrollView has shrunk to the new size and the Scrollable now works.
When keyboard is up, Scrollable.ensureVisible() does its job(here clicking on row 6 and 12)
I know this is similar to this question, but
I tried multiple things of this issue.
I use a "custom keyboard"
The flutter github issue here below fixed this (I think)
Read through this popular Flutter Github issue, this made me use SingleChildScrollView instead of ListView.
Tried this, this fixes the keyboard obscuring the bottom Rows by shifting them up, however now when clicking on the first Rows, they get moved out of view.
Tried KeyboardAvoider, but as this is not an onscreen Keyboard, I doesn't work.
You'll find a full minimal reproducible example here below.
main.dart
(Main + ChangeNotifierProvider for the state)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPage.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
void main() {
runApp(HomeScreen());
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ChangeNotifierProvider(
create: (_) => BudgetPageState(), child: BudgetPage()),
),
);
}
}
budgetPage.dart
(Main Page with the CustomList() and the buttonDial (custom keyboard, here just a simple container)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:scrollTest/customList.dart';
import 'package:provider/provider.dart';
class BudgetPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
BudgetPageState budgetPageState = Provider.of<BudgetPageState>(context, listen: true);
ButtonDial buttonDial =
budgetPageState.showButtonDial ? ButtonDial() : null;
return Scaffold(
appBar: AppBar(
title: Text("Budget Page"),
),
body: Column(children: <Widget>[
Expanded(child: CustomList()),
if (buttonDial != null) buttonDial
]));
}
}
class ButtonDial extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height * 0.3,
child: Container(
color: Colors.blue,
),
);
}
}
customList.dart
(Simple List view SingleChildScrollView and a ScrollController)
import 'package:flutter/material.dart';
import 'package:scrollTest/CustomRow.dart';
class CustomList extends StatefulWidget {
#override
_CustomListState createState() => _CustomListState();
}
class _CustomListState extends State<CustomList> {
ScrollController _scrollController;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scrollbar(
isAlwaysShown: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: _buildList(),
),
),
);
}
}
List<Widget> _buildList() {
List<Widget> widgetList = [];
for (int i = 0; i < 15; i++) {
widgetList.add(CustomRow(rowID: i));
}
return widgetList;
}
customRow.dart
(This is where I scroll to the selected row in handleOnTap)
import 'package:flutter/material.dart';
import 'package:scrollTest/budgetPageState.dart';
import 'package:provider/provider.dart';
class CustomRow extends StatefulWidget {
final int rowID;
CustomRow({Key key, #required this.rowID}) : super(key: key);
#override
_CustomRowState createState() => _CustomRowState();
}
class _CustomRowState extends State<CustomRow> {
BudgetPageState budgetPageState;
void handleOnTap(BuildContext context) {
if (!budgetPageState.isSelected(widget.rowID)) {
Scrollable.ensureVisible(context, duration: Duration(milliseconds: 200), alignment: 0.5);
}
budgetPageState.toggleButtonDial(widget.rowID);
budgetPageState.updateIsSelected(widget.rowID);
}
#override
void initState() {
super.initState();
budgetPageState = Provider.of<BudgetPageState>(context, listen: false);
budgetPageState.insertRowInHashMap(widget.rowID);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap:() => handleOnTap(context),
child: Container(
height: 60,
width: double.infinity,
color: budgetPageState.isSelected(widget.rowID)
? Colors.grey[200]
: Colors.white,
child: Center(
child: Text(
"Test ${widget.rowID}",
),
),
),
);
}
}
budgetPageState.dart
(The state managed using ChangeNotifier. Mainly contains logic for selecting/deselecting a row as well as logic for when to show the keyboard (using bool showButtonDial and notifyListeners())
import 'dart:collection';
import 'package:flutter/material.dart';
class BudgetPageState extends ChangeNotifier {
bool showButtonDial = false;
Map<int, bool> _isSelectedMap = HashMap();
int selectedId = -1;
bool isSelected(int rowId) {
return this._isSelectedMap[rowId];
}
Map<int, bool> get isSelectedMap => _isSelectedMap;
void updateIsSelected(int rowId) async {
///Select the row [rowId] if we tapped on a different one than the one
///that is currently highlighted ([selectedId])
///The same row was tapped, we remove the highlight i.e. we don't
///put it back to [true]
//Unselect all
_isSelectedMap.forEach((k, v) => _isSelectedMap[k] = false);
if (selectedId != rowId) {
this._isSelectedMap[rowId] = true;
selectedId = rowId;
} else {
selectedId = -1;
}
notifyListeners();
}
void toggleButtonDial(int rowId) {
if (!showButtonDial) {
showButtonDial = true;
} else if (rowId == selectedId) {
showButtonDial = false;
}
}
void insertRowInHashMap(int subcatId) {
this._isSelectedMap[subcatId] = false;
}
}
Related
I have a SingleChildScrollView. Sometimes its child is longer than the screen, in which case the SingleChildScrollView lets you scroll. But also sometimes its child is shorter than the screen, in which case no scrolling is needed.
I am trying to add an arrow to the bottom of the screen which hints to the user that they can/should scroll down to see the rest of the contents. I successfully implemented this except in the case where the child of the SingleChildScrollView is shorter than the screen. In this case no scrolling is needed, so I would like to not show the arrow at all.
I've tried making a listener to do this, but the listener isn't activated until you start scrolling, and in this case you can't scroll.
I've also tried accessing the properties of the _scrollController in the ternary operator which shows the arrow, but an exception is thrown: ScrollController not attached to any scroll views.
Here is a complete sample app showing what I'm doing, so you can just copy and paste it if you want to see it run. I replaced all of the contents with a Column of Text widgets for simplicity:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) =>
MaterialApp(home: Scaffold(body: MyScreen()));
}
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
ScrollController _scrollController = ScrollController();
bool atBottom = false;
#override
void initState() {
super.initState();
// Activated when you get to the bottom:
_scrollController.addListener(() {
if (_scrollController.position.extentAfter == 0) {
setState(() {
atBottom = true;
});
}
});
// Activated as soon as you start scrolling back up after getting to the bottom:
_scrollController.addListener(() {
if (_scrollController.position.extentAfter > 0 && atBottom) {
setState(() {
atBottom = false;
});
}
});
// I want this to activate if you are at the top of the screen and there is
// no scrolling to do, i.e. the widget being displayed fits in the screen:
_scrollController.addListener(() {
if (_scrollController.offset == 0 &&
_scrollController.position.extentAfter == 0) {
setState(() {
atBottom = false;
});
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.vertical,
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
for (int i = 0; i < 100; i++)
Text(
i.toString(),
),
],
),
),
),
atBottom
? Container()
: Positioned(
bottom: 10,
right: 10,
child: Container(
child: Icon(
Icons.arrow_circle_down,
),
),
),
],
);
}
}
Import the scheduler Flutter library:
import 'package:flutter/scheduler.dart';
Create a boolean flag inside the state object but outside of the build method to track whether build has been called yet:
bool buildCalledYet = false;
Add the following in the beginning of the build method:
if (!firstBuild) {
firstBuild = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() {
atBottom = !(_scrollController.position.maxScrollExtent > 0);
});
});
}
(The boolean flag prevents this code from causing build to be called over and over again.)
Here's the complete code of the sample app implementing this solution:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) =>
MaterialApp(home: Scaffold(body: MyScreen()));
}
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
ScrollController _scrollController = ScrollController();
bool atBottom = false;
// ======= new code =======
bool buildCalledYet = false;
// ========================
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.extentAfter == 0) {
setState(() {
atBottom = true;
});
}
});
_scrollController.addListener(() {
if (_scrollController.position.extentAfter > 0 && atBottom) {
setState(() {
atBottom = false;
});
}
});
// ======= The third listener is not needed. =======
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// =========================== new code ===========================
if (!buildCalledYet) {
buildCalledYet = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
setState(() {
atBottom = !(_scrollController.position.maxScrollExtent > 0);
});
});
}
// ================================================================
return Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.vertical,
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
for (int i = 0; i < 100; i++)
Text(
i.toString(),
),
],
),
),
),
atBottom
? Container()
: Positioned(
bottom: 10,
right: 10,
child: Container(
child: Icon(
Icons.arrow_circle_down,
),
),
),
],
);
}
}
I found this solution on another Stack Overflow question: Determine Scroll Widget height
I have created a ListView with container boxes as widgets. I want a specific container to expand onTap upto a specific screen height and width. I need help in implementing this in flutter. I have made a prototype on AdobeXD.
AdobeXD Prototype GIF
I am new to flutter, any kind of help is appreciated.
A flutter plugin called flutter swiper might help you achieve what you want to achieve.
Visit this pub dev and you can read documentation.
Here you go brother, Although its not blurring the background but I think it will get you going.
It's working something like this:
Below the code which you can copy paste. I have added comments in the code for understanding it in better way. Cheers :)
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeApp(),
);
}
}
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
// Items in the list --> Custom Widgets
List<Widget> arr = [
ListContainerHere(),
ListContainerHere(),
ListContainerHere(),
ListContainerHere(),
ListContainerHere(),
ListContainerHere(),
];
Widget getListWidget(List<Widget> items) {
List<Widget> list = new List<Widget>();
for (var i = 0; i <= items.length; i++) {
list.add(new ListContainerHere(
index: i,
));
}
return Row(children: list);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter App :)"),
),
body: Center(
// Using a 'Row' as Horizontal ListView
child: SingleChildScrollView(
scrollDirection: Axis.horizontal, child: getListWidget(arr)),
),
);
}
}
// Widgets that will be rendered in the Horizontal Row
class ListContainerHere extends StatefulWidget {
final int index;
ListContainerHere({this.index});
#override
_ListContainerHereState createState() => _ListContainerHereState();
}
class _ListContainerHereState extends State<ListContainerHere> {
// Varibale to change the height and width accordingly
// Initally no item will be expanded
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
// Changing the value of 'isExpanded' when an item is tapped in the List
setState(() {
isExpanded = !isExpanded;
});
},
// AnimatedContainer for slowing down the changing
child: AnimatedContainer(
duration: Duration(milliseconds: 150),
// Changing the width and height
height: isExpanded ? 250 : 150,
width: isExpanded ? 250 : 150,
// Decoration Portion of the Container
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(15.0)),
),
),
);
}
}
In my code below, I am struggling with LifeCyrles in Flutter where I can update my State in Provider, APPARENTLY, only in didChangeDependencies hook or in a template widget (via events hung up on buttons or so).
Alright, I don't mind that only didChangeDependencies hook works for me BUT when my logic in earlier mentioned hook depends on some class properties I am having problems with the accuracy of the class data.
I get data one step behind (since it's called before build hook I guess).
I cannot run this logic in the build hook because it includes a request to change the state in Provider. If I try to change the state there I've got either this error:
setState() or markNeedsBuild() called during build.
or this one
The setter 'lastPage=' was called on null.
Receiver: null
Tried calling: lastPage=true
What I want to do: I've got a wrapper widget which holds three other widgets: footer, header and pageViewer.
When I reach the last page I need to notify my wrapper widget about that so it reacts accordingly and hides header and footer.
I would appreciate any help here!
The focused code:
Here is the problem and must be solution
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';
class _FooterState extends State<Footer> {
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
// this._detectLastPage();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
],
),
);
}
_detectLastPage() {
// Here I've got inaccurate data
print(this.widget.currentStep);
}
}
I have already tried some other hooks like Scheduler but maybe I did something wrong there.
SchedulerBinding.instance
.addPostFrameCallback((_) => this._detectLastPage());
It's called only once at the first build-up round and that's it.
I lack an Angular hook here AfterViewInit. It would be handy here.
or Mounted in VueJS
That's the rest of my code if you'd like to see the whole picture.
If you have any suggestions on the architecture, structure or something else you are welcome. It's highly appreciated since I'm new to Flutter.
main.dart
import 'package:flutter/material.dart';
import 'package:ui_flutter/routing.dart';
import 'package:provider/provider.dart';
import 'screens/welcome/welcome_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => WelcomeBloc()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/welcome',
onGenerateRoute: RouteGenerator.generateRoute,
),
);
}
}
welcome.dart (my wrapper)
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import './footer.dart';
import './viewWrapper.dart';
import './header.dart';
// import 'package:ui_flutter/routing.dart';
class Welcome extends StatefulWidget {
#override
_WelcomeState createState() => _WelcomeState();
}
class _WelcomeState extends State<Welcome> {
WelcomeBloc _welcomeBloc;
#override
Widget build(BuildContext context) {
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
this._welcomeBloc = _welcome;
print('Welcome: _welcome.currentPage - ${this._welcomeBloc.lastPage}');
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
ViewerWrapper(),
Footer(
currentStep: _welcomeBloc.currentPage,
totalSteps: 3,
activeColor: Colors.grey[800],
inactiveColor: Colors.grey[100],
),
WelcomeHeader,
],
),
),
);
}
}
welcomeBloc.dart (my state via Provider)
import 'package:flutter/material.dart';
class WelcomeBloc extends ChangeNotifier {
PageController _controller = PageController();
int _currentPage;
bool _lastPage = false;
bool get lastPage => _lastPage;
set lastPage(bool value) {
_lastPage = value;
notifyListeners();
}
int get currentPage => _currentPage;
set currentPage(int value) {
_currentPage = value;
notifyListeners();
}
get controller => _controller;
nextPage(Duration duration, Curves curve) {
controller.nextPage(duration: duration, curve: curve);
}
}
footer.dart (that's where I've problems with data at the very bottom of the code - _detectLastPage method)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';
class Footer extends StatefulWidget {
final int currentStep;
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
Footer({
this.activeColor,
this.inactiveColor,
this.currentStep,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
}) {}
#override
_FooterState createState() => _FooterState();
}
class _FooterState extends State<Footer> {
final double radius = 10.0;
final double distance = 4.0;
Container stepper;
Container nextArrow;
bool lastPage;
WelcomeBloc _welcomeBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
this._detectLastPage();
}
#override
Widget build(BuildContext context) {
this._makeStepper();
this._makeNextArrow();
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
],
),
);
}
_makeCirle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep == null ? 0 : currentStep - 1;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: this.radius,
width: this.radius,
margin: EdgeInsets.only(left: this.distance, right: this.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeStepper() {
List<Container> circles = List();
for (var i = 0; i < widget.totalSteps; i++) {
circles.add(
_makeCirle(this.widget.activeColor, this.widget.inactiveColor, i,
this.widget.currentStep),
);
}
this.stepper = Container(
child: Row(
children: circles,
),
);
}
_makeNextArrow() {
this.nextArrow = Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
_welcomeBloc.controller.nextPage(
duration: this.widget.duration ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_onLastPage() {
if (this.widget.onFinal != null) {
this.widget.onFinal();
}
}
_onFirstPage() {
if (this.widget.onStart != null) {
this.widget.onStart();
}
}
_detectLastPage() {
// Here I've got inaccurate data
int currentPage =
this.widget.currentStep == null ? 1 : this.widget.currentStep;
if (currentPage == 1 && this.widget.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.widget.totalSteps) {
print('lastPage detected');
setState(() {
this.lastPage = true;
});
_welcomeBloc.lastPage = true;
this._onLastPage();
} else {
setState(() {
this.lastPage = false;
});
_welcomeBloc.lastPage = false;
}
}
}
Thanks in advance!
I am new to flutter as well, But I have learned about a few architecture patterns that have helped me build some apps.
Here is how I do it:
Create a Provider which holds the data for you in runtime. (It can be a Bloc in your case). Stick to one architecture, don't try to put providers and blocs in the same project. Both are used for state management and only using one would be a great practice.
Second, Register the providers using ChangeNotificationProvider or any other widgets which does a similar job of rebuilding the child widget when a data gets changed.
Third, Get the provider in the build method of the widget that is supposed to change when the value of the variable provider changes. This way only the concerned widget is redrawn.
For your case,
If you want to hide the header and footer once you reach the last page, you can declare a variable, let's say isLastPage set to false by default in your provider.
Next, wrap the widget, i.e. header and footer with ChangeNotificationListner
Now, let that widget decide what it has to do based on the value of isLastPage, either hide itself or show.
I hope this helps!
At the long run, I seem to have found Mounted lifecycle hook in Flutter which is implemented with the help of Future.microtask. Unlike .addPostFrameCallback:
SchedulerBinding.instance
.addPostFrameCallback((_) => this._detectLastPage());
Which is triggered only once like InitState (but only at the end of the build execution), Future.microtask can be placed inside build block and be invoked after every change and state update.
It doesn't solve the problem with the inaccurate state in didChangeDependencies hook but provides another way to perform post-build executions.
Credits for the current solution to #Abion47
example
Future.microtask(() => this._detectLastPage());
I am making an app which loads the CSV and show the table on the screen but the load function is being called infinitely in the build state can anyone know how to fix it I wanted to call only once but my code called it many times.
Here is the console screenshot:
Here is the code:
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
List<List<dynamic>> data = [];
loadAsset() async {
final myData = await rootBundle.loadString("asset/dreamss.csv");
List<List<dynamic>> csvTable = CsvToListConverter().convert(myData);
return csvTable;
}
void load() async{
var newdata = await loadAsset();
setState(() {
data = newdata;
});
print("am i still being called called ");
}
#override
Widget build(BuildContext context) {
load();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Apps"),),
//floatingActionButton: FloatingActionButton( onPressed: load,child: Icon(Icons.refresh),),
body: ListView(
children: <Widget>[
Container(margin: EdgeInsets.only(top: 20.0),),
Table(
border: TableBorder.all(width: 1.0,color: Colors.black),
children: data.map((item){
return TableRow(
children: item.map((row){
return Text(row.toString(),style: TextStyle(fontSize: 20.0,fontWeight: FontWeight.w900),);
}).toList(),
);
}).toList(),
),
]),
));
}
}
Here is the solution.
#override
void initState() {
super.initState();
load(); // use it here
}
#override
Widget build(BuildContext context) {
return MaterialApp(...); // no need to call initState() here
}
I'm building a simple messaging system, where the user will see a list of messages.
I have a ListView.Builder with reverse:true since I want the list to appear at the bottom when they load the messaging page.
When they pull down to scroll all the way to the top I want a refresh indicator to appear so they can load previous messages, like most popular chat applications do.
However due to having reverse:true on the list they have to pull up at the bottom of the screen to load previous messages while using a RefreshIndicator.
Is there a way to make the RefreshIndicator trigger when pulling down rather than up when using reverse:true?
In my opinion,do you want to load more at the bottom of the listview,i think you just need to add one load more view to the last item of the listview,like the following code:
import 'package:flutter/material.dart';
import 'dart:async';
void main() {
runApp(new MaterialApp(
home: new Scaffold(
body: new LoadMoreListView(enableLoadMore: true, count: 30,),
),
));
}
class LoadMoreListView extends StatefulWidget {
bool enableLoadMore;
int count;
LoadMoreListView({this.enableLoadMore = true, this.count = 15});
#override
State<StatefulWidget> createState() {
return new LoadMoreListViewState();
}
}
class LoadMoreListViewState extends State<LoadMoreListView> {
ScrollController _scrollController = new ScrollController();
bool isRequesting = false;
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
///load more when the listView attached the bottom
loadMore();
}
});
}
Future<Null> loadMore() async {
if (isRequesting) {
///if is requesting ,return the next action
return null;
}
setState(() {
isRequesting = true;
});
///loading your data from any where,eg:network
return null;
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new ListView.builder(
itemCount: _count(),
itemBuilder: _buildItem);
}
_count() {
if (widget.enableLoadMore) {
return widget.count + 1;
}
return widget.count;
}
Widget _buildItem(BuildContext context, int index) {
if (index == widget.count) {
return _buildLoadMoreView();
}
return new Container(
height: 36.0,
child: new Center(
child: new Text("I am the $index item"),
),
);
}
Widget _buildLoadMoreView() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: 1.0,
child: new CircularProgressIndicator(),
),
),
);
}
}