I got it working somehow, but the scroll feature is gone:
return Scaffold(
body: DefaultTabController(
length: 2,
child: ListView(
children: <Widget>[
Container(
height: 120,
child: Center(
child: Text('something on top'),
),
),
TabBar(
// controller: _tabController,
labelColor: Colors.redAccent,
isScrollable: true,
tabs: [
Tab(text: "Finished"), // TODO: translate
Tab(text: "In progress"), // TODO: translate
],
),
Center(
child: [
Text('second tab1232'),
Text('second tab111'),
Column(
children: List.generate(20, (index) => Text('line: $index'))
.toList(),
),
Text('third tab')
][0], // change this
),
Container(child: Text('another component')),
],
),
),
);
Note: check the [0] that I simplified.
Not sure if I can fix the scroll from this or if I need to take a totally different approach.
Example of content scroll working with the original way: https://flutter.dev/docs/cookbook/design/tabs
Check out this other answer solves the problem: https://stackoverflow.com/a/57383014/12334012
With this we don't have the swipe and its animation but it could be created by some other widgets. It works with a dynamic height anyway:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class OptionsScreen extends StatefulWidget {
const OptionsScreen({Key? key}) : super(key: key);
#override
OptionsScreenState createState() => OptionsScreenState();
}
class OptionsScreenState extends State<OptionsScreen> {
final bodyGlobalKey = GlobalKey();
late var streamFunction;
#override
void initState() {
streamFunction = delayFunction;
super.initState();
}
#override
void dispose() {
super.dispose();
}
Stream<int> delayFunction = (() async* {
for (int i = 1; i <= 3; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
yield i;
}
})();
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: streamFunction,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
if (!snapshot.hasData) return const Text('loading...');
if (snapshot.data == null) return Container();
// return tabs();
return const CustomTabs();
},
);
}
}
class CustomTabs extends StatefulWidget {
const CustomTabs({Key? key}) : super(key: key);
#override
State<CustomTabs> createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabs>
with SingleTickerProviderStateMixin {
late TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 3, vsync: this);
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TabBar(
isScrollable: true,
controller: _tabController,
// COMMENT FOR WAY B
onTap: (int index) {
setState(() {
_tabController.animateTo(index);
});
},
labelColor: Theme.of(context).primaryColor,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
),
),
tabs: [
Tab(text: '1'),
Tab(text: '2'),
Tab(text: '3'),
],
),
// WAY A
// THIS WAY HAS NO ANIMATION BUT IT HAS DYNAMIC HEIGHT
// SetState TRIGGERS STREAMBUILDER WITH TAB CHANGES
Column(
children: [
Visibility(
visible: _tabController.index == 0,
child: const Text('1111'),
),
Visibility(
visible: _tabController.index == 1,
child: Column(
children: const [
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
Text('2222'),
],
),
),
Visibility(
visible: _tabController.index == 2,
child: const Text('33333'),
),
const Text('This is a different widget. Tabs will push it down')
],
),
// THIS WAY HAS NO DYNAMIC HEIGHT BUT IT HAS ANIMATION
// STREAMBUILDER ONLY TRIGGERS FIRST TIME
// Container(
// color: Colors.red,
// height: 200,
// padding: const EdgeInsets.only(top: 8.0),
// child: TabBarView(
// controller: _tabController,
// children: [
// const Text('1111'),
// Column(
// children: const [
// Text('2222'),
// Text('2222'),
// Text('2222'),
// Text('2222'),
// ],
// ),
// const Text('3333'),
// ],
// ),
// ),
],
),
],
);
}
}
Related
When I made program about a news app, I got an error.
This my HomePage code
import 'dart:io';
import 'package:bubble_tab_indicator/bubble_tab_indicator.dart';
import 'package:carousel_slider/carousel_options.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:last_news/article.dart';
import 'package:last_news/network/api_request.dart';
import 'package:last_news/news.dart';
import 'package:last_news/state.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod/riverpod.dart';
class Home extends StatefulWidget {
Home({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin{
final List<Tab> tabs = <Tab>[
new Tab(text: 'General'),
new Tab(text: 'Technology'),
new Tab(text: 'Sport'),
new Tab(text: 'Business'),
new Tab(text: 'Entertainment'),
new Tab(text: 'Health'),
];
late TabController _tabController;
#override
void initState() {
// TODO: implement initState
super.initState();
_tabController = new TabController(length: tabs.length, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('News App'),
bottom: TabBar(
isScrollable: true,
unselectedLabelColor: Colors.grey,
labelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BubbleTabIndicator(
indicatorHeight: 25.0,
indicatorColor: Colors.blueAccent,
tabBarIndicatorSize: TabBarIndicatorSize.tab,
),
tabs: tabs,
controller: _tabController,
),
),
body: TabBarView(
controller: _tabController,
children: tabs.map((Tab){
return FutureBuilder(
future: fetchNewsByCategory(Tab.text!),
builder: (context, snapshot) {
if(snapshot.hasError)
return Center(child: Text('${snapshot.error}'),);
else if(snapshot.hasData)
{
var newsList = snapshot.data as News;
var sliderList = newsList.articles != null ?
newsList.articles!.length > 10 ?
newsList.articles!.getRange(0, 10).toList()
: newsList.articles!.take(newsList.articles!.length).toList()
: [];
var contentList = newsList.articles !=null ?
newsList.articles!.length > 10 ?
newsList.articles!.getRange(11, newsList.articles!.length - 1).toList()
: [] : [];
return SafeArea(child: Column(
children: [
CarouselSlider(
options: CarouselOptions(
aspectRatio: 16/9,
enlargeCenterPage: true,
viewportFraction: 0.8,
),
items: sliderList.map((item){
return Builder(builder: (context){
return GestureDetector(
onTap: () {
context.read(urlState).state = item.url;
Navigator.pushNamed(context, '/detail');
}, child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
'${item.urlToImage}',
fit: BoxFit.cover,),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Color(0xAA333639),
child: Padding(
padding: const EdgeInsets.all(8), child:
Text('${item.title}',
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
),
),
],
)
]),
);
});
}).toList(),
),
Divider(thickness: 3),
Padding(padding: const EdgeInsets.only(left: 8),
child: Text('Trending',
style: TextStyle(fontSize: 26,fontWeight: FontWeight.bold),
),
),
Divider(thickness: 3),
Expanded(child: ListView.builder(
itemCount: contentList.length,
itemBuilder:(context, index) {
return GestureDetector(onTap: (){
context.read(urlState).state = contentList[index].url;
Navigator.pushNamed(context, '/detail');
}, child:
ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network('${contentList[index].urlToImage}',
fit: BoxFit.cover,
height: 80,
width: 80,
),
),
title: Text('${contentList[index].title}',
style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text('${contentList[index].publishedAt}',
style: TextStyle(fontStyle: FontStyle.italic)),
),);
}))
],
));
}
else return Center(child: CircularProgressIndicator(),);
});
}).toList(),
),
);
}
}
And this my NewsDetail code (This code is linked to the HomePage code)
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:last_news/state.dart';
import 'package:webview_flutter/webview_flutter.dart';
class NewsDetail extends StatefulWidget {
#override
State<NewsDetail> createState() => _NewsDetailState();
}
class _NewsDetailState extends State<NewsDetail> {
double progress = 0;
final Completer<WebViewController> _controller = Completer<WebViewController>();
#override
void initState() {
// TODO: implement initState
super.initState();
if(Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context,) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Color(0xFFA51234)));
return MaterialApp(
home: Scaffold(
body: SafeArea(child: Container(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.arrow_back, color:Colors.black),
alignment: Alignment.topLeft,)
],),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(),
),
Expanded(child: WebView(
initialUrl: context.read(urlState).state,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_controller.complete(controller);
},
),)
],),)),),
);
}
}
I got an error on
(HomePage)
return Builder(builder: (context){
return GestureDetector(
onTap: () {
context.read(```
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra arguments.).state = item.url;
Navigator.pushNamed(context, '/detail');
},
And
Expanded(child: ListView.builder(
itemCount: contentList.length,
itemBuilder:(context, index) {
return GestureDetector(onTap: (){
context.read(**urlState**).state = contentList[index].url;
Navigator.pushNamed(context, '/detail');
},
(NewsDetail)
Expanded(child: WebView(
initialUrl: context.read(**urlState**).state,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
\_controller.complete(controller);
},
),
)
This Error I got
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra arguments.
This code urlState
import 'package:flutter_riverpod/flutter_riverpod.dart';
final urlState = StateProvider(((ref) =\> ''));
Please Help Me To Fix This Error
I tried adding import 'package:flutter_bloc/flutter_bloc.dart'; because the "context.read" have a red underline. And then, the "urlState" have a red underline.
When I made my first project about a news app, I got an error.
this is my code
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:last_news/state.dart';
import 'package:webview_flutter/webview_flutter.dart';
class NewsDetail extends StatefulWidget {
#override
State<NewsDetail> createState() => _NewsDetailState();
}
class _NewsDetailState extends State<NewsDetail> {
double progress = 0;
final Completer<WebViewController> _controller = Completer<WebViewController>();
#override
void initState() {
// TODO: implement initState
super.initState();
if(Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context,) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Color(0xFFA51234)));
return MaterialApp(
home: Scaffold(
body: SafeArea(child: Container(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(children: [
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.arrow_back, color:Colors.black),
alignment: Alignment.topLeft,)
],),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(),
),
Expanded(child: WebView(
initialUrl: context.read(urlState).state,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_controller.complete(controller);
},
),)
],),)),),
);
}
}
I got this error on initialUrl: context.read(urlState).state,
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra arguments.
This code urlState
import 'package:flutter_riverpod/flutter_riverpod.dart';
final urlState = StateProvider(((ref) => ''));
Please help me to fix this
I tried adding import 'package:flutter_bloc/flutter_bloc.dart'; because the "context.read" have a red underline. And then, the "urlState" have a red underline.
Another Error code on HomePage
import 'dart:io';
import 'package:bubble_tab_indicator/bubble_tab_indicator.dart';
import 'package:carousel_slider/carousel_options.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:last_news/article.dart';
import 'package:last_news/network/api_request.dart';
import 'package:last_news/news.dart';
import 'package:last_news/state.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod/riverpod.dart';
class Home extends StatefulWidget {
Home({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin{
final List<Tab> tabs = <Tab>[
new Tab(text: 'General'),
new Tab(text: 'Technology'),
new Tab(text: 'Sport'),
new Tab(text: 'Business'),
new Tab(text: 'Entertainment'),
new Tab(text: 'Health'),
];
late TabController _tabController;
#override
void initState() {
// TODO: implement initState
super.initState();
_tabController = new TabController(length: tabs.length, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('News App'),
bottom: TabBar(
isScrollable: true,
unselectedLabelColor: Colors.grey,
labelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BubbleTabIndicator(
indicatorHeight: 25.0,
indicatorColor: Colors.blueAccent,
tabBarIndicatorSize: TabBarIndicatorSize.tab,
),
tabs: tabs,
controller: _tabController,
),
),
body: TabBarView(
controller: _tabController,
children: tabs.map((Tab){
return FutureBuilder(
future: fetchNewsByCategory(Tab.text!),
builder: (context, snapshot) {
if(snapshot.hasError)
return Center(child: Text('${snapshot.error}'),);
else if(snapshot.hasData)
{
var newsList = snapshot.data as News;
var sliderList = newsList.articles != null ?
newsList.articles!.length > 10 ?
newsList.articles!.getRange(0, 10).toList()
: newsList.articles!.take(newsList.articles!.length).toList()
: [];
var contentList = newsList.articles !=null ?
newsList.articles!.length > 10 ?
newsList.articles!.getRange(11, newsList.articles!.length - 1).toList()
: [] : [];
return SafeArea(child: Column(
children: [
CarouselSlider(
options: CarouselOptions(
aspectRatio: 16/9,
enlargeCenterPage: true,
viewportFraction: 0.8,
),
items: sliderList.map((item){
return Builder(builder: (context){
return GestureDetector(
onTap: () {
context.read(urlState).state = item.url;
Navigator.pushNamed(context, '/detail');
}, child: Stack(children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
'${item.urlToImage}',
fit: BoxFit.cover,),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Color(0xAA333639),
child: Padding(
padding: const EdgeInsets.all(8), child:
Text('${item.title}',
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold
),
),
),
),
],
)
]),
);
});
}).toList(),
),
Divider(thickness: 3),
Padding(padding: const EdgeInsets.only(left: 8),
child: Text('Trending',
style: TextStyle(fontSize: 26,fontWeight: FontWeight.bold),
),
),
Divider(thickness: 3),
Expanded(child: ListView.builder(
itemCount: contentList.length,
itemBuilder:(context, index) {
return GestureDetector(onTap: (){
context.read(urlState).state = contentList[index].url;
Navigator.pushNamed(context, '/detail');
}, child:
ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network('${contentList[index].urlToImage}',
fit: BoxFit.cover,
height: 80,
width: 80,
),
),
title: Text('${contentList[index].title}',
style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text('${contentList[index].publishedAt}',
style: TextStyle(fontStyle: FontStyle.italic)),
),);
}))
],
));
}
else return Center(child: CircularProgressIndicator(),);
});
}).toList(),
),
);
}
}
This error on
return Builder(builder: (context){
return GestureDetector(
onTap: () {
context.read(urlState).state = item.url;
Navigator.pushNamed(context, '/detail');
},
And
Expanded(child: ListView.builder(
itemCount: contentList.length,
itemBuilder:(context, index) {
return GestureDetector(onTap: (){
context.read(urlState).state = contentList[index].url;
Navigator.pushNamed(context, '/detail');
},
Both of these codes have the same error
The webview widget accepts initialUrl as a string, It has no any method as you added above.
Therefore the code could be written
Expanded(
child: WebView(
initialUrl: '', // This should be string so you can get data and convert it to string
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_controller.complete(controller);
},
),
);
I am using the persistent_bottom_nav_bar package and have implemented its custom navigation bar (basically just customized from the example in their Readme page). Reproducible code below.
The issue: when you navigate with the bottom navigation, ALL the pages rebuild on every tap. Quite draining on the app's performance! This seems to be a Flutter issue in general and solutions are given by using e.g. an IndexedStack when the full code is written by oneself instead of using a package, which I have done.
Is there any way to fix this issue when using the persistent_bottom_nav_bar package and specifically with the custom code that I have used?
My code (simplified so that anyone can just copy & run it):
main.dart
import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar/persistent-tab-view.dart';
import 'page1.dart';
import 'page2.dart';
import 'page3.dart';
import 'page4.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: HomeScaffold(),
);
}
}
class HomeScaffold extends StatefulWidget {
#override
_HomeScaffoldState createState() => _HomeScaffoldState();
}
class _HomeScaffoldState extends State<HomeScaffold> {
PersistentTabController _controller;
#override
void initState() {
super.initState();
_controller = PersistentTabController(initialIndex: 0);
}
List<Widget> _buildScreens() {
return [
Page1(),
Page2(),
Page3(),
Page4(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
_buildBottomNavBarItem('Page 1', Icons.home),
_buildBottomNavBarItem('Page 2', Icons.search),
_buildBottomNavBarItem('Page 3', Icons.message),
_buildBottomNavBarItem('Page 4', Icons.settings),
];
}
#override
Widget build(BuildContext context) {
return PersistentTabView.custom(
context,
controller: _controller,
screens: _buildScreens(),
confineInSafeArea: true,
itemCount: 4,
handleAndroidBackButtonPress: true,
stateManagement: true,
screenTransitionAnimation: ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
customWidget: CustomNavBarWidget(
items: _navBarsItems(),
onItemSelected: (index) {
setState(() {
_controller.index = index;
});
},
selectedIndex: _controller.index,
),
// ),
);
}
}
class CustomNavBarWidget extends StatelessWidget {
final int selectedIndex;
final List<PersistentBottomNavBarItem> items;
final ValueChanged<int> onItemSelected;
CustomNavBarWidget({
Key key,
this.selectedIndex,
#required this.items,
this.onItemSelected,
});
Widget _buildItem(PersistentBottomNavBarItem item, bool isSelected) {
return Container(
alignment: Alignment.center,
height: kBottomNavigationBarHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: IconTheme(
data: IconThemeData(
size: 26.0,
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary == null
? item.activeColorPrimary
: item.inactiveColorPrimary),
child: isSelected ? item.icon : item.inactiveIcon ?? item.icon,
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Material(
type: MaterialType.transparency,
child: FittedBox(
child: Text(
item.title,
style: TextStyle(
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary,
fontWeight: FontWeight.w400,
fontSize: 12.0),
)),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Container(
width: double.infinity,
height: kBottomNavigationBarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items.map((item) {
int index = items.indexOf(item);
return Flexible(
child: GestureDetector(
onTap: () {
this.onItemSelected(index);
},
child: _buildItem(item, selectedIndex == index),
),
);
}).toList(),
),
),
);
}
}
PersistentBottomNavBarItem _buildBottomNavBarItem(String title, IconData icon) {
return PersistentBottomNavBarItem(
icon: Icon(icon),
title: title,
activeColorPrimary: Colors.indigo,
inactiveColorPrimary: Colors.grey,
);
}
try AutomaticKeepAliveClientMixin, this won't refresh page while change tab:
1.
class PageState extends State<Page> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
I'm trying to check if the keyboard is visible after tapping on the TextFormField by calling:
if (MediaQuery.of(context).viewInsets.bottom != 0) {
...
}
but as soon as I have this MediaQuery call in my code, the Keyboard doesn't even open anymore after tapping on the TextFormField...
Edited:
This is what happens when tapping on the TextFormField:
I added the code of the page which causes this faulty behavior:
class LearnPage extends StatefulWidget {
final int topicId;
final String topicName;
LearnPage(this.topicId, this.topicName);
#override
_LearnPageState createState() => _LearnPageState();
}
class _LearnPageState extends State<LearnPage> {
final mainCaardIndex = ValueNotifier<int>(0);
PageController _mainCaardController;
PageController _inputCaardController;
List<CaardM> caards;
List<PageM> mainCaardList = [];
List<List<PageM>> inputCaardList = [];
List<List<TextEditingController>> textControllers = [];
Future<void> async_init() async {
List<CaardM> caardList =
await DatabaseProviderCaard.db.getCaards(widget.topicId);
caards = caardList;
setState(() {});
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
#override
void initState() {
async_init();
_mainCaardController = PageController();
_inputCaardController = PageController();
super.initState();
}
#override
void dispose() {
_mainCaardController.dispose();
_inputCaardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlue,
title: Center(
child: Text(
widget.topicName,
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
actions: [
!_keyboardIsVisible()
? IconButton(
icon: Icon(Icons.check_circle_outline),
tooltip: 'Validate',
onPressed: validate,
)
: IconButton(
icon: Icon(Icons.keyboard_hide),
onPressed: () {
FocusManager.instance.primaryFocus.unfocus();
},
),
],
),
body: Column(
children: [
Expanded(
flex: 3,
child: FutureBuilder(
future: getMainContent(),
builder: (context, AsyncSnapshot<int> snapshotMain) {
if (snapshotMain.connectionState == ConnectionState.done) {
return PageView.builder(
itemCount: snapshotMain.data,
controller: _mainCaardController,
onPageChanged: (position) {
mainCaardIndex.value = position;
mainCaardIndex.notifyListeners();
_inputCaardController.jumpToPage(0);
},
itemBuilder: (context, position) {
return LearnMainCaard(
mainCaardList[position].title,
mainCaardList[position].content,
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
Expanded(
flex: 5,
child: FutureBuilder(
future: getInputContent(),
builder: (context, AsyncSnapshot<int> snapshotInput) {
if (snapshotInput.connectionState == ConnectionState.done) {
return ValueListenableBuilder(
valueListenable: mainCaardIndex,
builder: (context, value, _) {
return PageView.builder(
itemCount: snapshotInput.data,
controller: _inputCaardController,
itemBuilder: (context, position) {
return LearnInputCaard(
inputCaardList[mainCaardIndex.value][position].title,
textControllers[mainCaardIndex.value][position],
);
},
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
],
),
);
}
Future<int> getMainContent() async {
List<PageM> caardPages;
mainCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
mainCaardList.add(caardPages[0]);
}
}
return mainCaardList.length;
}
Future<int> getInputContent() async {
List<PageM> caardPages = [];
List<PageM> list = [];
inputCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
addController(caards[i].pageAmount - 1);
list = [];
for (var i = 1; i < caardPages.length; i++) {
list.add(caardPages[i]);
}
inputCaardList.add(list);
}
}
return inputCaardList[mainCaardIndex.value].length;
}
void addController(int controllerAmount) {
List<TextEditingController> currentTextControllers = [];
print('addController called');
currentTextControllers.clear();
currentTextControllers = List.generate(
controllerAmount, (index) => TextEditingController()
);
textControllers.add(currentTextControllers);
}
And here the LearnInputCaard widget:
import 'package:flutter/material.dart';
class LearnInputCaard extends StatefulWidget {
final String title;
final TextEditingController textController;
LearnInputCaard(
this.title,
this.textController,
);
#override
_LearnInputCaardState createState() => _LearnInputCaardState();
}
class _LearnInputCaardState extends State<LearnInputCaard> {
#override
Widget build(BuildContext context) {
return Container(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
margin: EdgeInsets.all(20),
color: Colors.amberAccent.shade100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
flex: 1,
child: Text(
widget.title,
style: TextStyle(fontSize: 20),
),
),
Divider(color: Colors.black38,),
Expanded(
flex: 10,
child: Container(
padding: EdgeInsets.all(10.0),
child: TextFormField(
controller: widget.textController,
maxLines: 30,
decoration: InputDecoration(
hintText: "Enter content",
border: InputBorder.none,
),
),
),
)
],
),
),
),
);
}
}
you need to check MediaQuery.of(context).viewInsets.bottom == 0.0
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Keyboard Visibility Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_keyboardIsVisible()
? Text(
"Keyboard is visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
: RichText(
text: TextSpan(children: [
TextSpan(
text: "Keyboard is ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
),
TextSpan(
text: "not ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.red),
),
TextSpan(
text: "visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
]),
),
SizedBox(
height: 20,
),
Container(
width: 200.0,
child: TextField(
style: Theme.of(context).textTheme.display1,
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
borderRadius: BorderRadius.circular(10.0),
),
),
),
)
],
),
));
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
}
The problem is that you get the context from the parent widget.
If you call:
MediaQuery.of(context);
in the same widget where your forms are, you shouldn't get this behavior.
You need to define a GlobalKey<FormState> in your highest widget and pass this one down. Then it works. I defined it first in my SafeArea and therefore it failed and I had the same problem with the keyboard.
Here are some snippets of my code. I have a PageController and use two different forms on my two pages.
class OnboardingScaffold extends HookConsumerWidget {
OnboardingScaffold({Key? key}) : super(key: key);
// here you define your GlobalKeys
final _formKeyLogin = GlobalKey<FormState>();
final _formKeyApply = GlobalKey<FormState>();
#override
Widget build(BuildContext context, WidgetRef ref) {
final controller = usePageController();
bool isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
return Scaffold(
body: Container(
padding: !isKeyboard
? const EdgeInsets.only(bottom: 80)
: const EdgeInsets.only(bottom: 0),
child: PageView(
controller: controller,
children: [
// here you pass these keys into your child Widget
LoginSafeArea(
formKey: _formKeyLogin,
),
ApplySafeArea(
formKey: _formKeyApply,
),
],
),
),
bottomSheet: !isKeyboard
? Container(height: 80)
: Container(height: 0),
);
}
}
The child Widget should contain a Form Widget:
class LoginSafeArea extends HookConsumerWidget {
const LoginSafeArea({Key? key, required this.formKey}) : super(key: key);
final GlobalKey<FormState> formKey;
#override
Widget build(BuildContext context, WidgetRef ref) {
return SafeArea(
child: Center(
child: Form(
key: formKey,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
const EmailFieldWidget(),
const SizedBox(height: 8.0),
const PasswordFieldWidget(),
const SizedBox(height: 16.0),
LoginButtonWidget(
formKey: formKey,
),
const SizedBox(height: 8.0),
],
),
),
),
),
);
}
}
I'm trying to create a screen that is contained within a pageview, that also contains a page view for part of the screen.
To acheive this I have an unlimited page view for the whole page itself, then every page has a header view, with a bottom half that has a page view with 3 possible options. I have this pretty much working, however, the pages I am using I would like a StreamBuilder... This is where the issue is caused.
class DiaryPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _DiaryPage();
}
class _DiaryPage extends State<DiaryPage> with TickerProviderStateMixin {
DiaryBloc _diaryBloc;
TabController _tabController;
PageController _pageController;
#override
void initState() {
_diaryBloc = BlocProvider.of<DiaryBloc>(context);
_diaryBloc.init();
_tabController = TabController(length: 3, vsync: this);
_pageController = PageController(initialPage: _diaryBloc.initialPage);
super.initState();
}
#override
void dispose() {
_diaryBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Flexible(
child: PageView.builder(
controller: _pageController,
itemBuilder: (BuildContext context, int position) {
return _buildPage(_diaryBloc.getDateFromPosition(position));
},
itemCount: _diaryBloc.amountOfPages,
),
);
}
Widget _buildPage(DateTime date) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[_getHeader(date), _getTabBody()],
);
}
Widget _getHeader(DateTime date) {
return Card(
child: SizedBox(
width: double.infinity,
height: 125,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8, 16, 8, 0),
child: Text(
'${DateFormat('EEEE').format(date)} ${date.day} ${DateFormat('MMMM').format(date)}',
style: Theme.of(context).textTheme.subtitle,
textScaleFactor: 1,
textAlign: TextAlign.center,
),
),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: () => {
_pageController.previousPage(
duration: Duration(milliseconds: 250),
curve: Curves.ease)
},
),
const Expanded(child: LinearProgressIndicator()),
IconButton(
icon: const Icon(Icons.chevron_right),
onPressed: () => {
_pageController.nextPage(
duration: Duration(milliseconds: 250),
curve: Curves.ease)
},
),
],
),
Container(
height: 40.0,
child: DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.white,
appBar: TabBar(
controller: _tabController,
unselectedLabelColor: Colors.grey[500],
labelColor: Theme.of(context).primaryColor,
tabs: const <Widget>[
Tab(icon: Icon(Icons.pie_chart)),
Tab(icon: Icon(Icons.fastfood)),
Tab(icon: Icon(Icons.directions_run)),
],
),
),
),
),
],
),
),
);
}
Widget _getTabBody() {
return Expanded(
child: TabBarView(
controller: _tabController,
children: <Widget>[
_getOverviewScreen(),
_getFoodScreen(),
_getExerciseScreen(),
],
),
);
}
// TODO - this seems to be the issue, wtf and why
Widget _getBody() {
return Flexible(
child: StreamBuilder<Widget>(
stream: _diaryBloc.widgetStream,
initialData: _diaryBloc.buildEmptyWidget(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
return snapshot.data;
},
),
);
}
Widget _getExerciseScreen() {
return Text("Exercise Screen"); //_getBody();
}
Widget _getFoodScreen() {
return Text("Food Screen"); //_getBody();
}
Widget _getOverviewScreen() {
return _getBody();
}
}
As you can see, there are three widgets being returned as part of the sub page view, 2 of them are Text Widgets which show correctly, but the StreamBuilder, which is populated correctly with another Text Widget seems to give me the red screen of death. Any ideas?
Fixed the problem, it was related to the StreamBuilder being wrapped in a Flexible rather than a column. I then added column to have a mainAxisSize of max... Seemed to work.
For custom ListView/PageView
In my case, I wanted to clear the list of my listview. In a custom ListView/PageView, the findChildIndexCallback will find the element's index after i.e. a reordering operation, but also when you clear the list.
yourList.indexWhere()unfortunately returns -1 when it couldn't find an element. So, Make sure to return null in that case, to tell the callback that the child doesn't exist anymore.
...
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key as ValueKey<String>;
final data = valueKey.value;
final index = images.indexWhere((element) => element.id == data);
//important here:
if (index > 0 ) return index;
else return null;
},