I want to achieve this animation with TextField:
but getting this instead:
Here is my TextField widget:
import 'package:flutter/material.dart'; import 'package:line_awesome_flutter/line_awesome_flutter.dart'; import 'package:move_me_delivery/data/styles.dart';
class SearchTextField extends StatefulWidget { const SearchTextField({Key? key,
this.onFocusChange,
this.focus,
this.onCancel,
this.inputDecoration }) : super(key: key);
final void Function(bool hasFocus)? onFocusChange; final FocusNode? focus; final VoidCallback? onCancel; final InputDecoration? inputDecoration;
#override _SearchTextFieldState createState() =>
_SearchTextFieldState(); }
class _SearchTextFieldState extends State<SearchTextField> { FocusNode _focus = new FocusNode();
#override void initState() {
super.initState();
_focus = widget.focus ?? new FocusNode();
_focus.addListener(
(){
if(widget.onFocusChange != null){
widget.onFocusChange!(_focus.hasFocus);
}
}
); }
#override Widget build(BuildContext context) {
return Hero(
tag: "search",
child: Row(
children: [
Expanded(
child: TextField(style: AppTextStyles.body2,
focusNode: _focus,
decoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
// suffixIcon: Text("Cancel"),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
))),
if(widget.onCancel != null)
GestureDetector(
onTap: widget.onCancel,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Cancel"),
),
)
],
),
); } }
And here is my first screen:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:move_me_delivery/components/rounded_app_bar.dart';
import 'package:move_me_delivery/components/search_field.dart';
import '../screens.dart';
class HomeTab extends StatelessWidget {
const HomeTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: RoundedAppBar(title: ""),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
onFocusChange: (val) async {
if(val){
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 3),
pageBuilder: (_, __, ___) => SearchScreen()));
// await Get.to(() => SearchScreen());
}
},
)
],
),
)
);
}
}
and here is my second screen:
import 'package:flutter/material.dart';
import 'package:line_awesome_flutter/line_awesome_flutter.dart';
import 'package:move_me_delivery/components/search_field.dart';
import 'package:move_me_delivery/data/styles.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
#override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _focusNode = FocusNode();
#override
void initState() {
super.initState();
_focusNode.requestFocus();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
focus: _focusNode,
onCancel: (){
FocusScope.of(context).unfocus();
Navigator.pop(context);
},
inputDecoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
),
),
],
),
),
),
),
);
}
}
My console:
======== Exception caught by widgets library =======================================================
The following assertion was thrown building TextField(focusNode: FocusNode#0fba2, decoration: InputDecoration(prefixIcon: Icon(IconData(U+0F002), color: Color(0xff000000)), filled: true, fillColor: Color(0xffffffff), border: OutlineInputBorder()), style: TextStyle(inherit: true, color: Color(0xff000000), size: 15.0, weight: 400, style: normal), dirty, dependencies: [MediaQuery, UnmanagedRestorationScope], state: _TextFieldState#43a28):
No Material widget found.
TextField widgets require a Material widget ancestor.
In material design, most widgets are conceptually "printed" on a sheet of material. In Flutter's material library, that material is represented by the Material widget. It is the Material widget that renders ink splashes, for instance. Because of this, many material library widgets require that there be a Material widget in the tree above them.
To introduce a Material widget, you can either directly include one, or use a widget that contains Material itself, such as a Card, Dialog, Drawer, or Scaffold.
The specific widget that could not find a Material ancestor was: TextField
focusNode: FocusNode#0fba2
decoration: InputDecoration(prefixIcon: Icon(IconData(U+0F002), color: Color(0xff000000)), filled: true, fillColor: Color(0xffffffff), border: OutlineInputBorder())
style: TextStyle(inherit: true, color: Color(0xff000000), size: 15.0, weight: 400, style: normal)
dirty
dependencies: [MediaQuery, UnmanagedRestorationScope]
state: _TextFieldState#43a28
The ancestors of this widget were:
: Expanded
flex: 1
: Row
direction: horizontal
mainAxisAlignment: start
crossAxisAlignment: center
dependencies: [Directionality]
renderObject: RenderFlex#afc02
: GetMaterialApp
: MyApp
...
The relevant error-causing widget was:
TextField file:///Users/akbarpulatov/Desktop/tests/move_me_delivery/lib/components/search_field.dart:49:20
When the exception was thrown, this was the stack:
#0 debugCheckHasMaterial.<anonymous closure> (package:flutter/src/material/debug.dart:27:7)
#1 debugCheckHasMaterial (package:flutter/src/material/debug.dart:48:4)
#2 _TextFieldState.build (package:flutter/src/material/text_field.dart:1116:12)
#3 StatefulElement.build (package:flutter/src/widgets/framework.dart:4691:27)
#4 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4574:15)
...
====================================================================================================
The error message you got already explains the problem very well, so I'll just give you a solution. Wrap the child of your Hero widget in a Material:
Hero(
tag: "search",
child: Material(
type: MaterialType.transparency,
child: Row(
children: [
//TextField(),
//GestureDetector(),
],
),
),
);
An alternative fix is to wrap the Inkwell in Material when the transition occurs:
Source: https://github.com/flutter/flutter/issues/34119
Hero(
tag: "image",
flightShuttleBuilder: (BuildContext flightContext, Animation<double> animation, HeroFlightDirection flightDirection, BuildContext fromHeroContext, BuildContext toHeroContext) => Material(child: toHeroContext.widget),
...
Related
I get an exception whenever I press the payments button on my bottom nav-bar.
the error goes like this:
The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a RenderObject, which has been set up to accept ParentData of incompatible type ParentData.
Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically, Expanded widgets are placed directly inside Flex widgets.
The offending Expanded is currently placed inside a RepaintBoundary widget.
The ownership chain for the RenderObject that received the incompatible parent data was:
SizedBox.shrink ← Expanded ← Spacer ← RepaintBoundary ← IndexedSemantics ← _SelectionKeepAlive ← NotificationListener<KeepAliveNotification> ← KeepAlive ← AutomaticKeepAlive ← KeyedSubtree ← ⋯
When the exception was thrown, this was the stack
one other issue that i am getting is that whenever i close the sliding up panel i want the bottom nav bar to go back to the personal screen and the personal index as well. right now in my code when i close the panel it remains in the payments tab.
code for the main page is:
import 'package:flutter/material.dart';
import 'package:sadapay_clone/screens/more_screen.dart';
import 'package:sadapay_clone/screens/payments.dart';
// import 'package:sliding_up_panel/sliding_up_panel.dart';
import '../widgets/homepage_item.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _currIndex = 0;
final tabs = [
const HomePageItem(),
const Payments(),
const MoreScreen(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 238, 237, 237),
body: tabs[_currIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home_rounded),
label: 'Personal',
),
BottomNavigationBarItem(
icon: Icon(Icons.view_kanban),
label: 'Payments',
),
BottomNavigationBarItem(
icon: Icon(Icons.menu),
label: 'More',
),
],
fixedColor: const Color.fromARGB(255, 255, 129, 129),
onTap: (index) {
setState(() {
_currIndex = index;
});
},
),
);
}
}
and the code for the payments page is:
import 'package:flutter/material.dart';
// import 'package:sadapay_clone/screens/homepage.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
import '../widgets/homepage_item.dart';
import '../widgets/tabwidget.dart';
class Payments extends StatefulWidget {
const Payments({super.key});
#override
State<Payments> createState() => _PaymentsState();
}
class _PaymentsState extends State<Payments> {
#override
Widget build(BuildContext context) {
return SlidingUpPanel(
backdropEnabled: true,
panelBuilder: (scrollController) =>
buildSlidingPanel(scrollController: scrollController),
body: const HomePageItem(),
// panel: const Text('this is sliding up panel'),
// borderRadius: BorderRadius.only(topRight: 20,topLeft: 20),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
backdropTapClosesPanel: true,
defaultPanelState: PanelState.OPEN,
// onPanelClosed: () => const MyHomePage(),
panelSnapping: false,
maxHeight: 400,
minHeight: 40,
);
}
Widget buildSlidingPanel({
required ScrollController scrollController,
}) =>
TabWidget(
scrollController: scrollController,
);
}
the tabwidget code is:
import 'package:flutter/material.dart';
import 'package:sadapay_clone/widgets/personal_item.dart';
class TabWidget extends StatelessWidget {
final ScrollController scrollController;
const TabWidget({super.key, required this.scrollController});
#override
Widget build(BuildContext context) => ListView(
padding: const EdgeInsets.all(16),
controller: scrollController,
children: const [
Padding(
padding: EdgeInsets.only(bottom: 20.0),
child: Icon(
Icons.horizontal_rule,
),
),
Spacer(),
Padding(
// padding: EdgeInsets.all(15.0),
padding: EdgeInsets.only(
top: 15,
left: 10,
bottom: 15,
),
child: Text(
'Payments',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
),
PersonalItem(
icon: Icon(
Icons.phone_android_rounded,
size: 30,
color: Color.fromARGB(255, 255, 129, 129),
),
title: 'Mobile top up',
subtext: 'Instantly top up your mobile.',
),
Divider(thickness: 1.5),
PersonalItem(
icon: Icon(
Icons.receipt_rounded,
size: 30,
color: Color.fromARGB(255, 255, 129, 129),
),
title: 'Bills and utilities',
subtext: 'Pay for your utilities.',
),
Divider(
thickness: 1.5,
),
PersonalItem(
icon: Icon(
Icons.card_membership_rounded,
size: 30,
color: Color.fromARGB(255, 255, 129, 129),
),
title: 'Money requests',
subtext: 'Review pending money req.',
),
// PersonalItem(),
// PersonalItem(),
],
);
}
This is my code for the main screen at which i want to retrieve the image name and the Login and signup words other than writing them with hard code from my realtime database. However here i always get the answer as null and the picture doesn't load. So what can i change in my code to get the values from my database without getting null. Moreover, my database is us central and i tried to put the url other than the instance and didn’t do any change.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(NGU_APP());
}
// ignore: camel_case_types
class NGU_APP extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomepage(),
routes: {
"login": (_) => LoginScreen(),
"signup": (_) => RegistrationScreen(),
},
);
}
}
class MyHomepage extends StatefulWidget {
const MyHomepage({key}) : super(key: key);
#override
_MyHomepageState createState() => _MyHomepageState();
}
class _MyHomepageState extends State<MyHomepage> {
DatabaseReference dbref;
String Images = "images/logo.png";
String Login = "Log In";
#override
void initState() {
super.initState();
dbref = FirebaseDatabase
.instance
.reference()
.child('ngu-su-default-rtdb');
dbref.child("Login").onValue.listen((event) {
print(event.snapshot.value.toString());
setState(() {
Login = event.snapshot.value.toString();
print(Login);
});
});
dbref.child("Imagees").onValue.listen((event) {
print(event.snapshot.value.toString());
setState(() {
Images = event.snapshot.value.toString();
print(Images);
});
});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color(0xFF6D0131),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Hero(
tag: 'logo',
child: CircleAvatar(
radius: (100),
child: ClipRRect(
borderRadius: BorderRadius.circular(110),
child: Image.asset("$Images"),
)),
),
),
],
),
SizedBox(
height: 48.0,
),
Buttons(
colour: Colors.white,
text: Login.toString(),
page: LoginScreen()),
SizedBox(
height: 5.0,
),
Buttons(
colour: Colors.white,
text: 'Sign Up',
page: RegistrationScreen())
],
),
),
),
),
),
);
}
}
class RoundedButton extends StatelessWidget {
RoundedButton({this.title, this.colour, #required this.onPressed});
final Color colour;
final String title;
final Function onPressed;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Material(
elevation: 5.0,
color: colour,
borderRadius: BorderRadius.circular(30.0),
child: MaterialButton(
onPressed: onPressed,
minWidth: 200.0,
height: 42.0,
child: Text(
title,
style: TextStyle(
fontSize: 20.0,
color: Color(0xFF6D0131),
),
),
),
),
);
}
}
class Buttons extends StatelessWidget {
Buttons({
#required this.colour,
#required this.text,
#required this.page,
});
final String text;
final Color colour;
final Widget page;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return page;
},
),
);
},
child: Container(
height: 50.0,
decoration: BoxDecoration(
color: colour,
borderRadius: BorderRadius.circular(20.0),
),
margin: EdgeInsets.only(bottom: 10.0),
padding: EdgeInsets.only(left: 18.0),
child: Center(
child: Text(
text,
style: TextStyle(
color: Color(0xFF6D0131),
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
This is the error i get:
I/flutter ( 5514): null
======== Exception caught by image resource service ================================================
The following assertion was thrown resolving an image codec:
Unable to load asset: null
When the exception was thrown, this was the stack:
#0 PlatformAssetBundle.load (package:flutter/src/services/asset_bundle.dart:224:7)
<asynchronous suspension>
#1 AssetBundleImageProvider._loadAsync (package:flutter/src/painting/image_provider.dart:675:14)
<asynchronous suspension>
Image provider: AssetImage(bundle: null, name: "null")
Image key: AssetBundleImageKey(bundle: PlatformAssetBundle#6f075(), name: "null", scale: 1.0)
====================================================================================================
I/chatty ( 5514): uid=10124(com.newgizauniversity.ngusu) 1.ui identical 2 lines
I/flutter ( 5514): null
I want to achieve this:
But when Hero animation starts keyboard is forced to dismiss:
I tried to use widgets callback which is triggered after layout. But this callback is fired whenever hero animation starts. I also tried to use Future.delayed(Duration(seconds: 2), but it does not help. Everything is working as expected if I only remove Hero widget from the widget tree.
Here is my first Screen:
import 'package:flutter/material.dart';
import 'package:move_me_delivery/components/rounded_app_bar.dart';
import 'package:move_me_delivery/components/search_field.dart';
import '../screens.dart';
class HomeTab extends StatelessWidget {
const HomeTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: RoundedAppBar(title: ""),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
onFocusChange: (val) async {
if(val){
await Navigator.push(context, PageRouteBuilder(
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) => SearchScreen()));
}
},
)
],
),
)
);
}
}
Here is my second screen:
import 'package:flutter/material.dart';
import 'package:line_awesome_flutter/line_awesome_flutter.dart';
import 'package:move_me_delivery/components/search_field.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
#override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _focusNode = FocusNode();
#override
void initState() {
super.initState();
_focusNode.requestFocus();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
focus: _focusNode,
onCancel: (){
FocusScope.of(context).unfocus();
Navigator.pop(context);
},
inputDecoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
),
),
],
),
),
),
),
);
}
}
And finally here is my SearchField screen with Hero animation:
import 'package:flutter/material.dart';
import 'package:line_awesome_flutter/line_awesome_flutter.dart';
import 'package:move_me_delivery/data/styles.dart';
class SearchTextField extends StatefulWidget {
const SearchTextField({Key? key,
this.onFocusChange,
this.focus,
this.onCancel,
this.inputDecoration
}) : super(key: key);
final void Function(bool hasFocus)? onFocusChange;
final FocusNode? focus;
final VoidCallback? onCancel;
final InputDecoration? inputDecoration;
#override
_SearchTextFieldState createState() => _SearchTextFieldState();
}
class _SearchTextFieldState extends State<SearchTextField>{
late FocusNode _focus;
#override
void initState() {
super.initState();
_focus = widget.focus ?? new FocusNode();
_focus.addListener(
(){
if(widget.onFocusChange != null){
widget.onFocusChange!(_focus.hasFocus);
}
}
);
}
#override
Widget build(BuildContext context) {
return Hero(
tag: "search",
child: Material(
type: MaterialType.card,
child: Row(
children: [
Expanded(
child: TextField(style: AppTextStyles.body2,
focusNode: _focus,
decoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
// suffixIcon: Text("Cancel"),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
))),
if(widget.onCancel != null)
GestureDetector(
onTap: widget.onCancel,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Cancel"),
),
)
],
),
),
);
}
}
The reason of keyboard dimiss is when hero animation start flight TextField unmounted so its focus loss and then keyboard dismiss.
And why TextFiled become to be unmounted, you need to understand how Hero Animaiton work, refer this https://docs.flutter.dev/development/ui/animations/hero-animations.
do something at the end of hero, you can do like below:
child: Hero(
tag: "hero_tag",
flightShuttleBuilder: ((flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// the end of hero animation end
_focusNode.requestFocus();
}
});
The interface you want to achieve doesn't necessarily use Hero widgets. It can be done with other animations. But, if you wan't to use Hero, you can try a rather hacky solution:
On your Screen 1, set these two properties in your Hero's TextField:
Hero(
tag: 'search',
child: Material(
type: MaterialType.transparency,
child: TextField(
readOnly: true,
showCursor: true,
onTap: () {
Navigator.push() //to SearchScreen()
}
),
),
),
Then, on Screen 2:
Hero(
tag: 'search',
child: Material(
type: MaterialType.transparency,
child: TextField(
autofocus: true,
),
),
),
You'll have to avoid using the same SearchTextField on both screens; they each need their own as I showed. Also, you can probably remove all of that FocusNode code if you use this method.
Disclaimer: I haven't tested this code. It's just something to try
I solved this exact same issue by creating a FocusNode that'll requestFocus at the end of the hero animation. However, it is also imperative that as a return for the flightShuttleBuilder function you return a widget similar to the one on the destination, except for the fact that it won't include this FocusNode.
Here's how it looks (on the destination page):
child: Hero(
tag: 'your_tag',
flightShuttleBuilder: (_, animation, __, ___, ____) {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_focusNode.requestFocus();
}
});
return TextField();
},
child: TextField(focusNode: _focusNode),
),
Can you please tell me what's the issue with my code and how to avoid showing that red screen error in my app?
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building ChatMessage(dirty):
RangeError (index): Invalid value: Valid value range is empty: 0
My Code:
import 'package:crypto_app/models/users.dart';
import 'package:crypto_app/pages/login.dart';
import 'package:crypto_app/services/firestoreservice.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
void main() {
runApp(
FriendlyChatApp(),
);
}
final ThemeData kIOSTheme = ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.grey[100],
primaryColorBrightness: Brightness.light,
);
final ThemeData kDefaultTheme = ThemeData(
primarySwatch: Colors.orange,
accentColor: Colors.orangeAccent,
);
String _name = '';
class FriendlyChatApp extends StatelessWidget {
const FriendlyChatApp({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ChatScreen(),
);
}
}
class ChatMessage extends StatelessWidget {
ChatMessage({this.text, this.animationController});
final String text;
final AnimationController animationController;
#override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor:
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(right: 16.0),
child: CircleAvatar(child: Text(_name[0])),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_name, style: Theme.of(context).textTheme.headline4),
Container(
margin: EdgeInsets.only(top: 5.0),
child: Text(text),
),
],
),
),
],
),
),
);
}
}
class ChatScreen extends StatefulWidget {
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
final List<ChatMessage> _messages = [];
final _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isComposing = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: Theme.of(context).platform == TargetPlatform.iOS //new
? BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey[200]),
),
)
: null,
child: Column(
children: [
Flexible(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
],
),
),
);
}
Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).accentColor),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Flexible(
child: TextField(
controller: _textController,
onChanged: (String text) {
setState(() {
_isComposing = text.isNotEmpty;
});
},
onSubmitted: _isComposing ? _handleSubmitted : null,
decoration: InputDecoration.collapsed(
hintText: 'Mesajınızı Buraya Yazınız:'),
focusNode: _focusNode,
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? CupertinoButton(
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
child: Text('Gönder'),
)
: IconButton(
icon: const Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_textController.text)
: null,
))
],
),
),
);
}
void _handleSubmitted(String text) {
_textController.clear();
setState(() {
_isComposing = false;
});
var message = ChatMessage(
text: text,
animationController: AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
),
);
setState(() {
_messages.insert(0, message);
});
_focusNode.requestFocus();
message.animationController.forward();
}
#override
void dispose() {
for (var message in _messages) {
message.animationController.dispose();
}
super.dispose();
}
}
The issue is happening because of this line:
CircleAvatar(child: Text(_name[0])),
You've declared the variable as:
String _name = '';
You are trying to access the first character of an empty string using the index. To fix the issue set some value to that variable:
String _name = 'Random';
I create a loading dialog and put it in StreamBuilder. At the same time, there is a method named _loadingText as the dialog parameter. When I click the 'Go Run' button, the _loadingText method is called twice.
As the same way, I used the flutter build-in dialog showAboutDialog, everything is OK.
If I remove the StreamBuilder, the _loadingText is called once too.
It takes me one day!!!
Any help is appreciated. Thanks in advance...
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:view_animation/loading_dialog.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<String> _streamController;
TextEditingController _inputController;
#override
void initState() {
super.initState();
_streamController = StreamController<String>.broadcast();
_inputController = TextEditingController();
_inputController.addListener(() {
_streamController.add(_inputController.text);
});
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
String _loadingText() {
print('===== 2. Method run OVER =====');
return 'Loading...';
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_inputContainer(),
SizedBox(
height: 20,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
),
child: StreamBuilder(
stream: _streamController.stream.map((text) => text.length > 4),
builder: (context, snap) {
return FlatButton(
color: Color(0xFFFFAC0B),
disabledColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12.5),
onPressed: snap.data != null && snap.data
? () {
print('===== 1. show dialog =====');
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(
loadingText: _loadingText(),
);
});
// showAboutDialog(context: context, applicationName: _loadingText());
}
: null,
child: Text(
'GO RUN',
style: TextStyle(fontSize: 12, color: Colors.white),
),
);
},
),
),
],
)),
);
}
Widget _inputContainer() {
return Container(
width: 200,
padding: EdgeInsets.only(left: 20, right: 20),
decoration: BoxDecoration(
color: Color(0xFFFFAC0B),
borderRadius: BorderRadius.circular(36.0),
),
child: TextField(
controller: _inputController,
keyboardType: TextInputType.number,
maxLines: 1,
cursorColor: Colors.orange,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Let's GO",
hintStyle: TextStyle(color: Colors.white54, fontSize: 20),
),
),
);
}
}
loading_dialog.dart
import 'package:flutter/material.dart';
class LoadingDialog extends StatefulWidget {
final String loadingText;
final bool outsideDismiss;
final Function dismissCallback;
final Future<dynamic> requestCallback;
LoadingDialog(
{Key key,
this.loadingText = "Loading...",
this.outsideDismiss = true,
this.dismissCallback,
this.requestCallback,
})
: super(key: key);
#override
_LoadingDialogState createState() => _LoadingDialogState();
}
class _LoadingDialogState extends State<LoadingDialog> {
void _dismissDialog(){
if(widget.dismissCallback != null) {
widget.dismissCallback();
}
Navigator.of(context).pop();
}
#override
void initState() {
print('===== 3. loading init =====');
if (widget.requestCallback != null) {
widget.requestCallback.then((_) => Navigator.of(context).pop());
}
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.outsideDismiss ? _dismissDialog : null,
child: Material(
type: MaterialType.transparency,
child: Center(
child: SizedBox(
width: 120.0,
height: 120.0,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
widget.loadingText,
style: new TextStyle(fontSize: 12.0),
),
),
],
),
),
),
),
),
);
}
}
log gif here
That's because when you tap on button first time your TextField is still active that means new state comes and flutter rebuilds itself. When you tap on button second your Textfield is inactive.
The points are when you pass the function to the onTap widget it's going to execute when it building state and calling a function without tapping on it:
So instead of a passing method to the OnTap, try something like this:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () widget.outsideDismiss ? ()
{
this._dismissDialog();
} : null,
...