I've been trying to customize Flutter SearchDelgate to the type of search field I want it to be. It got a method named appBarTheme with return type ThemeData. Usually using ThemeData you can change the appbar theme but it's not making any change in my case. I am able to customize the hint text style searchFieldStyle method but nothing more.
here is code:
class CustomSearchDelegate extends SearchDelegate<Country> {
#override
ThemeData appBarTheme(BuildContext context) {
return ThemeData(
appBarTheme: AppBarTheme(
elevation: 0,
color: themeColor,
//app bar color I wanted
),
);
}
#override
TextStyle get searchFieldStyle => TextStyle(
color: whiteTextColor,
fontWeight: FontWeight.w600,
fontFamily: GoogleFonts.poppins().fontFamily,
);
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(
Icons.close_rounded,
color: Colors.white,
),
onPressed: () => query = '',
),
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Column(
children: [],
);
}
#override
Widget buildSuggestions(BuildContext context) {
return Column(
children: [],
);
}
}
It would be super helpful if someone could help me out with this.
also, a similar question has been raised before but never got answered
Flutter create custom search UI extends SearchDelegate
I found one way to customize flutter search delegate the way you want. you just have to copy flutter's search delegates code and then customize the code you want.
Here is the Solution:
1: this is the code of showSearch.
Container(
padding: EdgeInsets.only(left: 15.w, right: 15.w, top: 15.h, bottom: 15.h),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.r)),
child: CustomSearchButton(
onTap: () async {
final String? result = await showSearchForCustomiseSearchDelegate(
context: context,
delegate: SearchScreen(
hintText: AppLocalizations.of(context)!.searchHere,
),
);
},
),
),
2: this is the code of customised flutter searchDelegate.
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = widget.delegate.appBarTheme(context);
final String searchFieldLabel = widget.delegate.searchFieldLabel ?? MaterialLocalizations.of(context).searchFieldLabel;
Widget? body;
switch (widget.delegate._currentBody) {
case _SearchBody.suggestions:
body = KeyedSubtree(
key: const ValueKey<_SearchBody>(_SearchBody.suggestions),
child: widget.delegate.buildSuggestions(context),
);
break;
case _SearchBody.results:
body = KeyedSubtree(
key: const ValueKey<_SearchBody>(_SearchBody.results),
child: widget.delegate.buildResults(context),
);
break;
case null:
break;
}
late final String routeName;
switch (theme.platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
routeName = '';
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
routeName = searchFieldLabel;
}
return Semantics(
explicitChildNodes: true,
scopesRoute: true,
namesRoute: true,
label: routeName,
child: Theme(
data: theme,
child: Scaffold(
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
leadingWidth: 0,
titleSpacing: 0,
//leading: widget.delegate.buildLeading(context),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(flex: 1, child: widget.delegate.buildLeading(context)!),
Expanded(
flex: 6,
child: Container(
margin: EdgeInsets.only(right: 15.w),
decoration: const BoxDecoration(
color: AppColors.white,
),
child: TextField(
controller: widget.delegate._queryTextController,
//focusNode: focusNode,
onSubmitted: (String _) {
widget.delegate.showResults(context);
},
textInputAction: widget.delegate.textInputAction,
keyboardType: widget.delegate.keyboardType,
decoration: InputDecoration(
fillColor: AppColors.white,
filled: true,
isDense: true,
hintText: searchFieldLabel,
hintStyle: TextStyle(fontSize: 14.sp),
contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
prefixIcon: widget.delegate._queryTextController.text.isNotEmpty
? null
: Padding(
padding: EdgeInsets.only(right: 5.w),
child: Image.asset(
AppImages.searchBoxIcon1,
scale: 3.5.sp,
),
),
suffixIcon: widget.delegate._queryTextController.text.isEmpty
? Image.asset(
AppImages.searchBoxIcon2,
scale: 3.5.sp,
)
: InkWell(
onTap: () {
widget.delegate._queryTextController.clear();
},
child: Image.asset(
AppImages.closeCircle,
scale: 3.5.sp,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.r)),
borderSide: const BorderSide(width: 1, color: AppColors.primaryColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.r)),
borderSide: const BorderSide(width: 1, color: AppColors.white),
),
border: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.primaryColor),
borderRadius: BorderRadius.all(Radius.circular(8.r)),
)),
),
// TextField(
// controller: widget.delegate._queryTextController,
// focusNode: focusNode,
// style: theme.textTheme.headline6,
// textInputAction: widget.delegate.textInputAction,
// keyboardType: widget.delegate.keyboardType,
// onSubmitted: (String _) {
// widget.delegate.showResults(context);
// },
// decoration: InputDecoration(hintText: searchFieldLabel),
// ),
),
),
],
),
actions: widget.delegate.buildActions(context),
bottom: widget.delegate.buildBottom(context),
),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: body,
),
),
),
);
3: Here is the full code.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:test_app/constants/app_images.dart';
import 'package:test_app/theme/colors.dart';
import 'package:test_app/widgets/custom_buttons.dart';
class SearchScreen extends SearchDelegate<String> {
SearchScreen({
String? hintText,
}) : super(
searchFieldLabel: hintText,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
);
#override
List<Widget>? buildActions(BuildContext context) {
return [
Container(),
];
}
#override
Widget? buildLeading(BuildContext context) {
return CustomBackButton(onTap: () {
close(context, '');
});
}
#override
Widget buildResults(BuildContext context) {
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
Future<T?> showSearchForCustomiseSearchDelegate<T>({
required BuildContext context,
required SearchDelegate<T> delegate,
String? query = '',
bool useRootNavigator = false,
}) {
assert(delegate != null);
assert(context != null);
assert(useRootNavigator != null);
delegate.query = query ?? delegate.query;
delegate._currentBody = _SearchBody.suggestions;
return Navigator.of(context, rootNavigator: useRootNavigator).push(_SearchPageRoute<T>(
delegate: delegate,
));
}
abstract class SearchDelegate<T> {
SearchDelegate({
this.searchFieldLabel,
this.searchFieldStyle,
this.searchFieldDecorationTheme,
this.keyboardType,
this.textInputAction = TextInputAction.search,
}) : assert(searchFieldStyle == null || searchFieldDecorationTheme == null);
Widget buildSuggestions(BuildContext context);
Widget buildResults(BuildContext context);
Widget? buildLeading(BuildContext context);
List<Widget>? buildActions(BuildContext context);
PreferredSizeWidget? buildBottom(BuildContext context) => null;
ThemeData appBarTheme(BuildContext context) {
assert(context != null);
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
assert(theme != null);
return theme.copyWith(
appBarTheme: AppBarTheme(
brightness: colorScheme.brightness,
backgroundColor: colorScheme.brightness == Brightness.dark ? Colors.grey[900] : Colors.white,
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
textTheme: theme.textTheme,
),
inputDecorationTheme: searchFieldDecorationTheme ??
InputDecorationTheme(
hintStyle: searchFieldStyle ?? theme.inputDecorationTheme.hintStyle,
border: InputBorder.none,
),
);
}
String get query => _queryTextController.text;
set query(String value) {
assert(query != null);
_queryTextController.text = value;
queryTextController.selection = TextSelection.fromPosition(TextPosition(offset: queryTextController.text.length));
}
void showResults(BuildContext context) {
_focusNode?.unfocus();
currentBody = SearchBody.results;
}
void showSuggestions(BuildContext context) {
assert(_focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
_focusNode!.requestFocus();
currentBody = SearchBody.suggestions;
}
void close(BuildContext context, T result) {
_currentBody = null;
_focusNode?.unfocus();
Navigator.of(context)
..popUntil((Route<dynamic> route) => route == _route)
..pop(result);
}
final String? searchFieldLabel;
final TextStyle? searchFieldStyle;
final InputDecorationTheme? searchFieldDecorationTheme;
final TextInputType? keyboardType;
final TextInputAction textInputAction;
Animation<double> get transitionAnimation => _proxyAnimation;
// The focus node to use for manipulating focus on the search page. This is
// managed, owned, and set by the _SearchPageRoute using this delegate.
FocusNode? _focusNode;
final TextEditingController _queryTextController = TextEditingController();
final ProxyAnimation _proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);
final ValueNotifier<_SearchBody?> _currentBodyNotifier = ValueNotifier<_SearchBody?>(null);
SearchBody? get currentBody => _currentBodyNotifier.value;
set _currentBody(_SearchBody? value) {
_currentBodyNotifier.value = value;
}
SearchPageRoute<T>? route;
}
enum _SearchBody {
suggestions,
results,
}
class _SearchPageRoute<T> extends PageRoute<T> {
_SearchPageRoute({
required this.delegate,
}) : assert(delegate != null) {
assert(
delegate._route == null,
'The ${delegate.runtimeType} instance is currently used by another active '
'search. Please close that search by calling close() on the SearchDelegate '
'before opening another search with the same delegate instance.',
);
delegate._route = this;
}
final SearchDelegate<T> delegate;
#override
Color? get barrierColor => null;
#override
String? get barrierLabel => null;
#override
Duration get transitionDuration => const Duration(milliseconds: 300);
#override
bool get maintainState => false;
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return FadeTransition(
opacity: animation,
child: child,
);
}
#override
Animation<double> createAnimation() {
final Animation<double> animation = super.createAnimation();
delegate._proxyAnimation.parent = animation;
return animation;
}
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return _SearchPage<T>(
delegate: delegate,
animation: animation,
);
}
#override
void didComplete(T? result) {
super.didComplete(result);
assert(delegate._route == this);
delegate._route = null;
delegate._currentBody = null;
}
}
class _SearchPage<T> extends StatefulWidget {
const _SearchPage({
required this.delegate,
required this.animation,
});
final SearchDelegate<T> delegate;
final Animation<double> animation;
#override
State<StatefulWidget> createState() => _SearchPageState<T>();
}
class _SearchPageState<T> extends State<_SearchPage<T>> {
FocusNode focusNode = FocusNode();
#override
void initState() {
super.initState();
widget.delegate._queryTextController.addListener(_onQueryChanged);
widget.animation.addStatusListener(_onAnimationStatusChanged);
widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
focusNode.addListener(_onFocusChanged);
widget.delegate._focusNode = focusNode;
}
#override
void dispose() {
super.dispose();
widget.delegate._queryTextController.removeListener(_onQueryChanged);
widget.animation.removeStatusListener(_onAnimationStatusChanged);
widget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
widget.delegate._focusNode = null;
focusNode.dispose();
}
void _onAnimationStatusChanged(AnimationStatus status) {
if (status != AnimationStatus.completed) {
return;
}
widget.animation.removeStatusListener(_onAnimationStatusChanged);
if (widget.delegate._currentBody == _SearchBody.suggestions) {
focusNode.requestFocus();
}
}
#override
void didUpdateWidget(_SearchPage<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.delegate != oldWidget.delegate) {
oldWidget.delegate._queryTextController.removeListener(_onQueryChanged);
widget.delegate._queryTextController.addListener(_onQueryChanged);
oldWidget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
oldWidget.delegate._focusNode = null;
widget.delegate._focusNode = focusNode;
}
}
void _onFocusChanged() {
if (focusNode.hasFocus && widget.delegate._currentBody != _SearchBody.suggestions) {
widget.delegate.showSuggestions(context);
}
}
void _onQueryChanged() {
setState(() {
// rebuild ourselves because query changed.
});
}
void _onSearchBodyChanged() {
setState(() {
// rebuild ourselves because search body changed.
});
}
#override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = widget.delegate.appBarTheme(context);
final String searchFieldLabel = widget.delegate.searchFieldLabel ?? MaterialLocalizations.of(context).searchFieldLabel;
Widget? body;
switch (widget.delegate._currentBody) {
case _SearchBody.suggestions:
body = KeyedSubtree(
key: const ValueKey<_SearchBody>(_SearchBody.suggestions),
child: widget.delegate.buildSuggestions(context),
);
break;
case _SearchBody.results:
body = KeyedSubtree(
key: const ValueKey<_SearchBody>(_SearchBody.results),
child: widget.delegate.buildResults(context),
);
break;
case null:
break;
}
late final String routeName;
switch (theme.platform) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
routeName = '';
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
routeName = searchFieldLabel;
}
return Semantics(
explicitChildNodes: true,
scopesRoute: true,
namesRoute: true,
label: routeName,
child: Theme(
data: theme,
child: Scaffold(
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
leadingWidth: 0,
titleSpacing: 0,
//leading: widget.delegate.buildLeading(context),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(flex: 1, child: widget.delegate.buildLeading(context)!),
Expanded(
flex: 6,
child: Container(
margin: EdgeInsets.only(right: 15.w),
decoration: const BoxDecoration(
color: AppColors.white,
),
child: TextField(
controller: widget.delegate._queryTextController,
//focusNode: focusNode,
onSubmitted: (String _) {
widget.delegate.showResults(context);
},
textInputAction: widget.delegate.textInputAction,
keyboardType: widget.delegate.keyboardType,
decoration: InputDecoration(
fillColor: AppColors.white,
filled: true,
isDense: true,
hintText: searchFieldLabel,
hintStyle: TextStyle(fontSize: 14.sp),
contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
prefixIcon: widget.delegate._queryTextController.text.isNotEmpty
? null
: Padding(
padding: EdgeInsets.only(right: 5.w),
child: Image.asset(
AppImages.searchBoxIcon1,
scale: 3.5.sp,
),
),
suffixIcon: widget.delegate._queryTextController.text.isEmpty
? Image.asset(
AppImages.searchBoxIcon2,
scale: 3.5.sp,
)
: InkWell(
onTap: () {
widget.delegate._queryTextController.clear();
},
child: Image.asset(
AppImages.closeCircle,
scale: 3.5.sp,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.r)),
borderSide: const BorderSide(width: 1, color: AppColors.primaryColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.r)),
borderSide: const BorderSide(width: 1, color: AppColors.white),
),
border: OutlineInputBorder(
borderSide: const BorderSide(color: AppColors.primaryColor),
borderRadius: BorderRadius.all(Radius.circular(8.r)),
)),
),
// TextField(
// controller: widget.delegate._queryTextController,
// focusNode: focusNode,
// style: theme.textTheme.headline6,
// textInputAction: widget.delegate.textInputAction,
// keyboardType: widget.delegate.keyboardType,
// onSubmitted: (String _) {
// widget.delegate.showResults(context);
// },
// decoration: InputDecoration(hintText: searchFieldLabel),
// ),
),
),
],
),
actions: widget.delegate.buildActions(context),
bottom: widget.delegate.buildBottom(context),
),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: body,
),
),
),
);
}
}
4: OUTPUT
Output video
Unfortunately, you do not have full control over the Theme of the AppBar in the SearchDelegate since some of the theme property values that you specify in the appBarTheme are not assigned to the app bar widget used in SearchDelegate. You can take a look at the source code. It only takes the values specified in the ThemeData specified in MaterialApp theme property. In my case, I needed to change the cursor color but changing the color in the MaterialApp would also modify the color in TextFields used elsewhere.
One solution is you can change the color before even opening the SearchDelegate i.e before showSearch and change it back again to the original color after navigating back from showSearch.
Related
I am working on a large-scale project and integrating video calls into it. So I made a separate project just for practice and I achieved good results. The group calling worked perfectly on both android and IOS. But then I integrated the same code in my large-scale project which uses firebase as a backend and when I navigate to the video screen it gives me an error saying "Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized". The Agora console is on Testing for now and the channel wasn't expired just in case you guys are wondering. As I said it works perfectly in a separate project.
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
class VideoCallPage extends StatefulWidget {
const VideoCallPage({Key? key}) : super(key: key);
#override
VideoCallPageState createState() => VideoCallPageState();
}
class VideoCallPageState extends State<VideoCallPage> {
static final _users = <int>[];
Logger logger = Logger();
bool muted = false;
late RtcEngine _engine;
bool isRigning = true;
bool isSpeakerOn = true;
final String channelName = 'video';
final String appID = 'xxx';
final String tokenAudio ='xxx';
#override
void dispose() {
_dispose();
super.dispose();
}
Future<void> _dispose() async {
_users.clear();
await _engine.leaveChannel();
await _engine.stopPreview();
await _engine.release();
}
#override
void initState() {
super.initState();
// initialize agora sdk
initialize();
}
Future<void> initialize() async {
logger.i('Initialize');
if (appID.isEmpty) {
setState(() {
logger.e(
'APP_ID missing, please provide your APP_ID in settings.dart',
);
logger.e('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
onOffSpeaker();
await _engine.joinChannel(
token: tokenAudio,
channelId: channelName,
uid: 0,
options: const ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileCommunication,
clientRoleType: ClientRoleType.clientRoleBroadcaster));
}
Future<void> _initAgoraRtcEngine() async {
logger.i('_initAgoraRtcEngine');
//create the engine
_engine = createAgoraRtcEngine();
logger.i('RtcEngineContext');
await _engine.initialize(RtcEngineContext(
appId: appID,
));
logger.i('enablbing video');
await _engine.enableVideo();
// await _engine.setVideoEncoderConfiguration(
// const VideoEncoderConfiguration(
// dimensions: VideoDimensions(width: 640, height: 360),
// frameRate: 15,
// bitrate: 0,
// ),
// );
await _engine.startPreview();
}
void _addAgoraEventHandlers() {
_engine.registerEventHandler(RtcEngineEventHandler(
onError: (ErrorCodeType errorCodeType, String value) {
if (mounted) {
setState(() {
final info = 'onError: ${errorCodeType.name}';
logger.e(info);
});
}
},
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {
final info =
'onJoinChannel: ${connection.channelId}, uid: ${connection.localUid}';
logger.i(info);
});
},
onLeaveChannel: (RtcConnection rtcConnection, RtcStats rtcStats) {
setState(() {
logger.i('onLeaveChannel');
_users.clear();
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
setState(() {
isRigning = false;
final info = 'remoteUserJoined: $remoteUid';
logger.i(info);
_users.add(remoteUid);
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
setState(() {
final info =
'remoteUserOffline: $remoteUid , reason: ${reason.index}';
logger.i(info);
_users.remove(remoteUid);
});
},
onFirstRemoteVideoFrame: (connection, uid, width, height, elapsed) {
setState(() {
final info = 'firstRemoteVideoFrame: $uid';
logger.i(info);
});
},
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Group Video Calling'),
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
Opacity(
opacity: isRigning ? 0.2 : 1,
child: _viewRows(),
),
_toolbar(),
if (isRigning)
Positioned(
top: 100,
left: MediaQuery.of(context).size.width * 0.3,
right: MediaQuery.of(context).size.width * 0.3,
child: Center(
child: Text(
'Ringing...',
style: TextStyle(color: Colors.white, fontSize: 30),
),
))
],
),
),
);
}
/// Helper function to get list of native views
List<Widget> _getRenderViews() {
final List<StatefulWidget> list = [];
list.add(AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
)));
_users.forEach((int uid) => list.add(AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: uid),
connection: RtcConnection(channelId: channelName),
),
)));
return list;
}
/// Video view wrapper
Widget _videoView(view) {
return Expanded(child: Container(child: view));
}
/// Video view row wrapper
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}
Widget _viewRows() {
final views = _getRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[_videoView(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow([views[0]]),
_expandedVideoRow([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 4))
],
));
default:
}
return Container();
}
Widget _toolbar() {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 48),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: onOffSpeaker,
child: Icon(
isSpeakerOn ? Icons.volume_up_sharp : Icons.volume_off,
color: isSpeakerOn ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: isSpeakerOn ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: _onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: _onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
);
}
void _onToggleMute() {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted);
}
void _onCallEnd(BuildContext context) {
Navigator.pop(context);
}
void _onSwitchCamera() {
_engine.switchCamera();
}
Future onOffSpeaker() async {
setState(() {
isSpeakerOn = !isSpeakerOn;
});
await _engine.setEnableSpeakerphone(isSpeakerOn);
}
}
I used Logger package to view logs and I found out the error occurs in this piece of code
await _engine.initialize(RtcEngineContext(
appId: appID,
));
The app uses agora_rtc_engine: ^6.1.0 and defined in pubspec.yaml file
In Agora SDK, there must be a late field requestPort which is accessed before the initialization.
It seems already similar issue raised in Agora SDK github repo. But its closed.
You can open a new issue or reopen existing with steps to reproduce the issue.
I'm working on an application to process a list of items using a bluetooth barcode scanner. When the page loads the user will have to scan the location barcode and if it is correct the focus will shift to the item barcode but while the item Widget shows up I get ViewPostIme key 1/0 in my console log and nothing happens.
Furthurmore while the shifting from Location to Item Widget focus works, it looks very glitchy like the soft keyboard animation is shown and hidden multiple times quickly.
inventPickTrans.dart
class InventPickTrans {
String itemId = "";
String itemName = "";
String itemPackBarCode = "";
String itemUnitBarCode = "";
String wmsLocationId = "";
pick_picking_sceen.dart
import 'package:provider/provider.dart';
import 'package:app/picking_provider.dart';
import 'package:flutter/material.dart';
import 'package:app/NoKeyboardEditableText.dart';
import 'package:app/inventPickTrans.dart';
List<InventPickTrans>? lstInventPickTrans;
class PickPickingScreen extends StatefulWidget {
static const String routeName = '/pick_picking_screen';
PickPickingScreen();
#override
_PickPickingScreenState createState() => _PickPickingScreenState();
}
class AlwaysDisabledFocusNode extends FocusNode {
#override
bool get hasFocus => false;
}
class _PickPickingScreenState extends State<PickPickingScreen>
with SingleTickerProviderStateMixin{
TextEditingController locationInputController = TextEditingController();
TextEditingController itemInputController = TextEditingController();
FocusNode focusNodeLocation = NoKeyboardEditableTextFocusNode();
FocusNode focusNodeItem = NoKeyboardEditableTextFocusNode();
#override
void initState() {
super.initState();
}
#override
void dispose() {
context.read<PickingProvider>().setItemInitialised(false);
focusNodeLocation.dispose();
focusNodeItem.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final inventPickTrans = context.read<PickingProvider>().workingInventPickTrans;
int itemIndex = context.read<PickingProvider>().itemIndex;
initPicklistSetup();
return Scaffold(
backgroundColor: Color.fromARGB(255, 29,161,125),
body: Container(
child: Column( //Outer
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
customRow(context, itemIndex, inventPickTrans![itemIndex]),
],
),
),
);
}
customRow(BuildContext context, int itemIndex, InventPickTrans inventPickTrans) {
if (context.read<PickingProvider>().isPicked[itemIndex]){
//PICKED
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(
color: Color.fromARGB( 255, 29, 161, 125)
),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
width: MediaQuery.of(context).size.width,
child: Center(
child: Text('PICKED',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40,
color: Color.fromARGB(255, 29, 161, 125),
fontWeight: FontWeight.w900,
),
),
),
)
)
)
]
);
}else if (!context.read<PickingProvider>().isLocationConfirmed[itemIndex]) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.white,
),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
width: MediaQuery.of(context).size.width,
child: Center(
child: Text('Scan Location: ' +
inventPickTrans.wmsLocationId.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40,
color: Color.fromARGB(
255, 29, 161, 125),
fontWeight: FontWeight.w900,
),
),
),
),
),
),
SizedBox(
width: 0,
height: 0,
child: NoKeyboardEditableText(
autofocus: true,
controller: locationInputController,
focusNode: focusNodeLocation,
textInputAction: TextInputAction.next,
style: TextStyle(),
cursorColor: Colors.white,
onSubmitted: (value) {
setState(() {
if (inventPickTrans.wmsLocationId == value) {
context.read<PickingProvider>().setLocationConfirmed(
true, itemIndex);
print('location scan success');
FocusScope.of(context).requestFocus(focusNodeItem);
} else {
context.read<PickingProvider>().setLocationConfirmed(
false, itemIndex);
print('location scan failure');
locationInputController.clear();
FocusScope.of(context).requestFocus(focusNodeLocation);
}
});
},
),
),
],
);
}else if(context.read<PickingProvider>().isLocationConfirmed[itemIndex] && !context.read<PickingProvider>().isItemConfirmed[itemIndex]){
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.white,
),
borderRadius: BorderRadius.all(Radius.circular(5)),
),
width: MediaQuery.of(context).size.width,
child: Center(
child: Text('Scan Item: ' + inventPickTrans.itemId.toString() + ' ' + inventPickTrans.itemName.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40,
color: Color.fromARGB(255, 29, 161, 125),
fontWeight: FontWeight.w900,
),
),
),
),
),
),
SizedBox(
width: 0,
height: 0,
child: NoKeyboardEditableText(
autofocus: true,
controller: itemInputController,
focusNode: focusNodeItem,
textInputAction: TextInputAction.next,
style: TextStyle(),
cursorColor: Colors.white,
onSubmitted: (value) {
setState(() {
if (inventPickTrans.itemUnitBarCode == value || inventPickTrans.itemPackBarCode == value) {
context.read<PickingProvider>().setItemConfirmed(
true, itemIndex);
FocusScope.of(context).unfocus();
print('item scan success');
} else {
context.read<PickingProvider>().setItemConfirmed(
false, itemIndex);
itemInputController.clear();
FocusScope.of(context).requestFocus(focusNodeItem);
print('item scan failure');
}
});
},
),
),
],
);
}else if(context.read<PickingProvider>().isLocationConfirmed[itemIndex] && context.read<PickingProvider>().isItemConfirmed[itemIndex]){
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
width: MediaQuery.of(context).size.width,
child: Center(
child: Icon(
Icons.check_circle_rounded,
color: Colors.yellow,
size: 40.0,
)
),
)
)
)
]
);
}else{
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
width: MediaQuery.of(context).size.width,
child: Center(
child: Icon(
Icons.refresh_rounded,
color: Colors.white,
size: 40.0,
)
),
)
)
)
]
);
}
}
}
class NoKeyboardEditableTextFocusNode extends FocusNode {
#override
bool consumeKeyboardToken() {
return false;
}
}
NoKeyboardEditableText.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NoKeyboardEditableText extends EditableText {
NoKeyboardEditableText({
required TextEditingController controller,
required TextStyle style,
required Color cursorColor,
required onSubmitted(String value),
required FocusNode focusNode,
required TextInputAction textInputAction,
//required onChanged(String value),
bool autofocus = true,
Color? selectionColor
}):super(
controller: controller,
focusNode: focusNode,
style: style,
cursorColor: cursorColor,
autofocus: autofocus,
selectionColor: selectionColor,
backgroundCursorColor: Colors.black,
//onChanged: onChanged,
onSubmitted: onSubmitted,
textInputAction: textInputAction,
);
#override
EditableTextState createState() {
return NoKeyboardEditableTextState();
}
}
class NoKeyboardEditableTextState extends EditableTextState {
#override
void requestKeyboard() {
super.requestKeyboard();
//hide keyboard
SystemChannels.textInput.invokeMethod('TextInput.hide');
}
}
class NoKeyboardEditableTextFocusNode extends FocusNode {
#override
bool consumeKeyboardToken() {
// prevents keyboard from showing on first focus
return false;
}
}
picking_provider.dart
import 'package:flutter/material.dart';
import 'package:app/inventPickTrans.dart';
class PickingProvider with ChangeNotifier{
bool _itemInitialised = false;
bool _picklistInitialised = false;
List<bool> _isLocationConfirmed = [false];
List<bool> _isItemConfirmed = [false];
List<bool> _isPicked = [false];
List<InventPickTrans>? _workingInventPickTrans = [];
int _itemIndex = 0;
List<bool> get isLocationConfirmed => _isLocationConfirmed;
void setLocationConfirmed(bool _clc, int _ii){
_isLocationConfirmed[_ii] = _clc;
notifyListeners();
}
void initLocationConfirmed(List<bool> _c){
_isLocationConfirmed.clear();
_isLocationConfirmed.insertAll(_isLocationConfirmed.length, _c);
notifyListeners();
}
List<bool> get isItemConfirmed => _isItemConfirmed;
void setItemConfirmed(bool _cip, int _ii){
_isItemConfirmed[_ii] = _cip;
notifyListeners();
}
void initItemConfirmed(List<bool> _c){
_isItemConfirmed.clear();
_isItemConfirmed.insertAll(_isItemConfirmed.length, _c);
notifyListeners();
}
List<InventPickTrans>? get workingInventPickTrans => _workingInventPickTrans;
void setWorkingInventPickTrans(List<InventPickTrans>? _wrol){
_workingInventPickTrans = [];
for(final e in _wrol!){
InventPickTrans line = InventPickTrans.fromJson(e.toJson());
_workingInventPickTrans!.add(line);
}
notifyListeners();
}
bool get itemInitialised => _itemInitialised;
void setItemInitialised(bool _ii){
_itemInitialised = _ii;
notifyListeners();
}
bool get picklistInitialised => _picklistInitialised;
void setPicklistInitialised(bool _pi){
_picklistInitialised = _pi;
notifyListeners();
}
int get itemIndex => _itemIndex;
void setItemIndex(int _ii){
_itemIndex = _ii;
notifyListeners();
}
List<bool> get isPicked => _isPicked;
void initIsPicked(List<bool> _s){
_isPicked.clear();
_isPicked.insertAll(isPicked.length, _s);
notifyListeners();
}
bool getIsItemPicked( int _ii){
return _isPicked[_ii];
}
void initPicklistSetup( List<InventPickTrans> InventPickTrans){
if (!picklistInitialised){
setWorkingInventPickTrans(InventPickTrans);
if (isPicked.length < workingInventPickTrans!.length) {
List<bool> fillSelected = (List.filled(workingInventPickTrans!.length, false));
initIsPicked(fillSelected);
}
if (isItemConfirmed.length < workingInventPickTrans!.length) {
List<bool> fillEditable = List.filled(workingInventPickTrans!.length - isItemConfirmed.length, false);
initItemConfirmed(fillEditable);
}
if (isLocationConfirmed.length < workingInventPickTrans!.length) {
List<bool> fillEditable = List.filled(workingInventPickTrans!.length - isLocationConfirmed.length, false);
initLocationConfirmed(fillEditable);
}
setPicklistInitialised(true);
}
}
}
I am new to flutter and cant understand why its giving me an error in routing, my login page
class _LoginPageState extends State<LoginPage> {
final _key = GlobalKey<FormState>();
String uid = "";
bool onLog = false;
toCat() async{
if(_key.currentState!.validate()){
setState(() {
onLog = true;
});
await Future.delayed(Duration(milliseconds: 1000));
Navigator.pushNamed(context, MyRoutes.catPage);
setState(() {
onLog = false;
});
}
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
key: _key,
child: Column(
children: [
SizedBox(height: 50,),
Image.network('https://www.pngkit.com/png/full/6-60441_welcome-welcome-png.png', fit: BoxFit.cover,),
SizedBox(height: 20,),
Text('Hi, $uid', style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),),
SizedBox(height: 20,),
Padding(
padding: EdgeInsets.all(30),
child: Column(
children: [
TextFormField(
decoration: InputDecoration(hintText: 'Enter username', labelText: 'Username'),
onChanged: (value){
uid = value;
setState(() {});
},
),
TextFormField(
obscureText: true,
decoration: InputDecoration(hintText:'Enter password', labelText: 'Password'),
validator: (value) {
if(value!.isEmpty){
return 'password must not be empty';
}else if(value.length < 6){
return 'passowrd cannot be small';
}else{return null;}
},
),
SizedBox(height: 25,),
InkWell(
onTap: (){toCat();},
child: AnimatedContainer(
alignment: Alignment.center,
duration: Duration(milliseconds: 300),
height: 50,
width: onLog ? 50 : 150,
decoration: BoxDecoration(color: Colors.cyan, borderRadius: BorderRadius.circular(onLog ? 50 : 8),),
child: onLog ? Icon(Icons.done) : Text('Login', style: TextStyle(color: Colors.black, fontSize: 20,),textScaleFactor: 1.2,),
),
onTap function in Inkwell widget , i wanna go to this page
class _CatPageState extends State<CatPage> {
Future<ModelClass> getImage() async {
final Uri uri = Uri.parse("https://aws.random.cat/meow");
final response = await (http.get(uri));
if(response.statusCode == 200){
final jsonData = jsonDecode(response.body);
return ModelClass.fromJson(jsonData);
}
else{
throw Exception;
}
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<ModelClass>(future: getImage(),builder: (context, snapshot) {
if (snapshot.hasData){
final cat = snapshot.data;
return Container(
height: 400,
width: 400,
decoration: BoxDecoration(image: DecorationImage(image: NetworkImage(cat!.url), fit: BoxFit.cover,),),
);
}else if(snapshot.hasError){
return Text(snapshot.error.toString());
}
return CircularProgressIndicator();
}
either that error in the title or this
Could not find a generator for route RouteSettings
or this
Navigator operation requested with a context that does not include a Navigator.
i cant understand routing
I managed to fix the problem with the following:
Nav()
{ Navigator.push(context, MaterialPageRoute(builder: (context) => LoginForm())); setState(){};
}
How i get focus of virtual keyboard rather than normal keyboard on textfiled .
heres the code
import 'dart:async';
import 'package:android/GlobalVariables.dart' as globals;
import 'package:flutter/material.dart';
import 'package:localstorage/localstorage.dart';
import 'Confirm.dart';
import 'package:virtual_keyboard/virtual_keyboard.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/services.dart';
import 'HomeScreen.dart';
void bh(context) async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
print("Connected to Mobile Network");
} else if (connectivityResult == ConnectivityResult.wifi) {
print("Connected to WiFi");
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Center(
child: Text(
"Connectivity ",
style: TextStyle(fontSize: 15),
),
),
actions: <Widget>[
Center(
child: Text(
"Please Check your Internet Connection",
style: TextStyle(fontSize: 15),
)),
FlatButton(
child: Text(
'ok',
style: TextStyle(fontSize: 15),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
}
}
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => new _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
LocalStorage userKey = new LocalStorage('userKey');
TextEditingController pinNumber = new TextEditingController();
bool hasKey;
var userKeyValue;
void showInSnackBar(String message) {
scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
}
#override
void initState() {
super.initState();
bh(context);
}
void checkKey() async {
userKeyValue = await userKey.getItem('userKey');
print(userKeyValue);
if (userKeyValue == null) {
hasKey = false;
} else {
hasKey = true;
}
}
void sendDataNav(BuildContext context) {
String textToSend = pinNumber.text;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Confirm(text: textToSend),
));
}
var study_no;
var select_mon;
LocalStorage mon = LocalStorage('mon');
LocalStorage studyNumber = LocalStorage('UserstudyNumber');
void start(context) async {
select_mon = DateTime.now().toString().substring(5, 7);
print("study number $study_no");
if (study_no == null) {
print("in $study_no");
Future.delayed(Duration.zero, () {
Navigator.pushNamed(context, '/setting');
});
} else {
print(select_mon);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(stu: study_no, mon: select_mon),
));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
key: scaffoldKey,
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height / 2.5,
width: MediaQuery.of(context).size.width,
color: globals.primaryColor,
child: Image.asset('image/asset/Logob.png'),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Container(
width: MediaQuery.of(context).size.width,
child: TextField(
controller: pinNumber,
style: new TextStyle(
fontSize: 20.0,
),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 20),
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.green, style: BorderStyle.solid),
borderRadius: new BorderRadius.horizontal(),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: globals.primaryColor,
style: BorderStyle.solid),
borderRadius: new BorderRadius.horizontal(),
),
//icon: Icon(Icons.calendar_today,),
hintText: 'PIN', suffixStyle: TextStyle(fontSize: 10),
labelText: 'Enter Your Pin',
),
// Navigator.pushNamed(context, '/afterCalender')
// keyboardType: TextInputType.number,
// autofocus: true,
onSubmitted: (ss) async {
if (pinNumber.text.length < 1) {
showInSnackBar("Enter Pin");
print('invalid');
return;
}
userKeyValue = await userKey.getItem('userKey');
if (userKeyValue == null) {
hasKey = false;
} else {
hasKey = true;
}
if (hasKey) {
if (userKeyValue != pinNumber.text) {
// show error message
showInSnackBar("Wrong Pin");
print('not valid');
} else {
print('valid');
study_no =
studyNumber.getItem('UserstudyNumber').toString();
start(context);
showInSnackBar("Sucessful");
}
} else {
if (pinNumber.text.length != 4) {
showInSnackBar("Enter Pin of 4 Numbers");
// show error message of length
print('hello');
} else {
setState(() {
sendDataNav(context);
});
// userKey.setItem('userKey', pinNumber.text);
}
}
},
),
),
),
VirtualKeyboard(
height: 300,
fontSize: 15,
textColor: Colors.black,
type: VirtualKeyboardType.Numeric,
onKeyPress: (key) => print(key.text)),
],
),
),
);
}
}
How i get focus of virtual keyboard rather than normal keyboard on textfiled .
onclick on textfiled i want system keyboard disabled and virtual keyboard to get focus and allow to add data..........................................................................................................................................................................................................................
When the onKeyPress callback get fired, you can set the text using the pinNumber text controller.
Hi I have a screen for adding images. I use the multi_image_picker for picking the images. Upon selection of the images, all images picked will be shown in a grid view with a FloatingActionButton on top of each image for removal of each image. The problem is, when I click the button for removing an image, the last image is removed not the one I clicked on. Has anyone experienced this?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:krakatoa/api/FileApi.dart';
import 'package:krakatoa/components/AssetView.dart';
import 'package:krakatoa/config/Themes.dart' as themes;
import 'package:krakatoa/mixins/SnackBarMixin.dart';
import 'package:krakatoa/podos/User.dart';
import 'package:krakatoa/utils/Common.dart' as common;
import 'package:multi_image_picker/multi_image_picker.dart';
class AddImageScreen extends StatefulWidget {
final List<String> currentImageUrls;
final String postUrl;
AddImageScreen({this.currentImageUrls, #required this.postUrl});
#override
State<StatefulWidget> createState() => _AddImageState();
}
class _AddImageState extends State<AddImageScreen> with SnackBarMixin {
List<Asset> _images = [];
User _user;
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Add images'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"Upload Images:",
style: Theme.of(context).textTheme.headline,
),
),
Container(
padding: EdgeInsets.only(bottom: 10),
child: GridView.count(
shrinkWrap: true,
crossAxisSpacing: 3,
mainAxisSpacing: 3,
crossAxisCount: 3,
children: _renderPickedImages(),
),
),
Container(
child: FlatButton(
child: Text('UPLOAD'),
padding: EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: themes.primaryColor,
onPressed: _onUploadBtnPress,
),
),
],
),
),
);
}
#override
void dispose() {
super.dispose();
for (var image in _images) {
image.release();
}
}
#override
void initState() {
super.initState();
common.getCurrentUser().then((user) {
if (user != null) {
_user = user;
} else {
_user = User();
}
});
}
void showProgressAlert() {
showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(
value: null,
),
title: Text('Processing...'),
),
),
);
},
barrierDismissible: false,
);
}
void _onAddImageBtnPress() async {
List<Asset> resultList;
try {
resultList = await MultiImagePicker.pickImages(maxImages: 5, enableCamera: true);
} on PlatformException catch (e) {
debugPrint("AddImageScreen._onAddImageBtnPress: ${e.toString()}");
}
if (!mounted) return;
if (resultList.isNotEmpty) {
setState(() {
_images.addAll(resultList);
});
}
}
void _onUploadBtnPress() {
if (_images.isNotEmpty) {
showProgressAlert();
_uploadImages();
} else {
showSnackBarMessage("No images to upload", seconds: 5);
}
}
void _removeImage(int index, Asset image) {
debugPrint("INDEX: $index");
debugPrint("Orig Image: ${_images[index].hashCode}");
setState(() {
_images.removeAt(index);
});
}
List<Widget> _renderPickedImages() {
List<Widget> imageWidgets = [];
imageWidgets.add(InkWell(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child: Center(
child: Icon(
Icons.add,
size: 60,
color: Colors.grey,
),
),
),
onTap: _onAddImageBtnPress,
));
var ctr = 0;
for (var image in _images) {
imageWidgets.add(Container(
child: Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
AssetView(image),
Positioned(
bottom: 0,
right: 0,
child: _ImageRemoveButton(
index: ctr,
removeItem: _removeImage,
image: image,
),
),
],
),
));
ctr++;
}
return imageWidgets;
}
Future<void> _uploadImages() async {
if (_user.id <= 0) {
showSnackBarMessage("User is not logged in");
Navigator.of(context).pop("User is not logged in");
return;
}
try {
await FileApi.uploadImages(widget.postUrl, _images);
Navigator.of(context).pop();
Navigator.of(context).pop("Success");
} on Exception catch (e) {
debugPrint(e.toString());
showSnackBarMessage(e.toString());
Navigator.of(context).pop(e.toString());
}
}
}
class _ImageRemoveButton extends StatelessWidget {
final Function removeItem;
final Asset image;
final int index;
_ImageRemoveButton({this.removeItem, this.index, this.image});
#override
Widget build(BuildContext context) {
return FloatingActionButton(
backgroundColor: Colors.white,
mini: true,
heroTag: "ImageAction_$index",
isExtended: false,
child: Icon(
Icons.close,
size: 15,
color: Colors.black,
),
onPressed: _onPress,
);
}
void _onPress() {
debugPrint("${this.hashCode}");
debugPrint("Passed Image: ${image.hashCode}");
removeItem(index, image);
}
}
add key: Key(YOUR_LIST.length.toString()),
having KEY helps with the update of the ListView
that worked for me on ListView.builder