flutter animated image is so laggy - flutter

import 'dart:math';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:image_sequence_animator/image_sequence_animator.dart';
import 'package:kane/model/KaneType.dart';
class Kane extends StatefulWidget {
Kane({Key key}) : super(key: key);
#override
KaneState createState() => KaneState();
}
class KaneState extends State<Kane> {
int _noseCount = 0;
bool _isHover = false;
bool _isPlaying = false;
KaneType _kaneType = KaneType.Kane;
List<GlobalKey<ImageSequenceAnimatorState>> animatorKeyList = [
GlobalKey(),
GlobalKey(),
GlobalKey(),
GlobalKey(),
GlobalKey()
];
AudioCache _cache = AudioCache();
AudioPlayer _player;
#override
Widget build(BuildContext context) {
ScreenUtil.init(context);
Widget kane;
switch (_kaneType) {
case KaneType.Kane:
kane = _kaneAnimation("kane", 8, 15, animatorKeyList[0], true);
break;
case KaneType.Ricardo:
kane = _kaneAnimation("ricardo", 212, 15, animatorKeyList[1], false);
break;
case KaneType.SexyKane:
kane = _kaneAnimation("sexyKane", 9, 15, animatorKeyList[2], true);
break;
case KaneType.MoemoeKane:
kane = _kaneAnimation("moemoe", 137, 24, animatorKeyList[3], false);
break;
default:
kane = _kaneAnimation("hanwha", 203, 24, animatorKeyList[4], false);
}
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: ScreenUtil().setHeight(800),
child: kane,
),
),
_kaneType == KaneType.Kane && !_isPlaying
? Positioned(
top: ScreenUtil().setHeight(918),
left: ScreenUtil().setWidth(506),
right: ScreenUtil().setWidth(506),
child: InkWell(
child: _isHover
? ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.grey[400], BlendMode.modulate),
child: Image.asset(
"assets/kane/kane/nose.webp",
width: ScreenUtil().setHeight(40),
height: ScreenUtil().setHeight(40),
fit: BoxFit.contain,
))
: Image.asset(
"assets/kane/kane/nose.webp",
width: ScreenUtil().setHeight(40),
height: ScreenUtil().setHeight(40),
fit: BoxFit.contain,
),
onTap: () {
setState(() => _isHover = false);
if (++_noseCount >= Random().nextInt(5) + 3) {
_noseCount = 0;
_cache.play('music/igonan.m4a');
} else {
_cache.play('music/bbolong.mp3');
}
},
onTapDown: (_) => setState(() => _isHover = true),
onTapCancel: () => setState(() => _isHover = false),
),
)
: Container()
],
);
}
Widget _kaneAnimation(String name, double frameCount, double fps,
GlobalKey<ImageSequenceAnimatorState> key, bool rewind) {
bool first = true;
return InkWell(
child: ImageSequenceAnimator(
"assets/kane/$name",
"$name",
0,
frameCount.toString().length - 2,
"webp",
frameCount,
key: key,
fps: fps,
isAutoPlay: false,
color: null,
onFinishPlaying: (animator) {
if (rewind && first) {
key.currentState.rewind();
first = false;
} else {
setState(() {
_isPlaying = false;
first = true;
});
key.currentState.reset();
}
},
),
onTap: () async {
if (!_isPlaying) {
setState(() {
_isPlaying = true;
});
_player = await _cache.play('music/$name.mp3');
key.currentState.play();
} else {
setState(() {
_isPlaying = false;
first = true;
});
_player.stop();
key.currentState.reset();
}
},
);
}
changeKane(KaneType kaneType) {
setState(() => _kaneType = kaneType);
}
}
I made an animation, but some devices are lagging.
https://www.youtube.com/watch?v=oScBgRUp1B8&feature=youtu.be
The animation is lagging too much.
InkWell(
child: Image.asset(
"assets/kane/moemoe/moemoe$_imageCount.webp",
),
onTap: () async {
for (int j = 1; j <= 136; j++) {
await Future.delayed(const Duration(milliseconds: 42), () {
setState(() => _imageCount++);
});
}
},
)
It's lagging even in this way. Also lag seems to only appear on some smartphones such as Galaxy a30, s7, and s9. p.s I am not good at English.
enter image description here The total size of the image is 3mb.

You are calling setState(() => _imageCount++); inside a for loop. Be careful when using this method! Calling setState, according to the documentation:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
The means, this for loop:
for (int j = 1; j <= 136; j++) {
await Future.delayed(const Duration(milliseconds: 42), () {
setState(() => _imageCount++);
});
}
Might be the cause of the lag. Every loop, you are telling your application that something has changed, and it needs to rebuild all the elements inside the build() function. And that might not be the only cause of lag, as I see you use setState((){}) a lot.

Related

Flutter rest api getting double result in pagination

I am using my rest API to get data in the app I used HTTP I write some code for pagination. the pagination is working good but when my all data get returned it's not show a message to all data is returned else its starts duplicate data from the top again. it's creating the duplicate data again pagination not stopping its returning the same data again and agin
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_web_browser/flutter_web_browser.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:convert';
import "package:http/http.dart" as http;
import 'package:voterlist/Widgets/adhelper.dart';
import 'package:voterlist/Widgets/constant.dart';
import 'package:voterlist/Widgets/skeleton.dart';
import 'package:voterlist/Widgets/widget.dart';
class Jobs extends StatefulWidget {
const Jobs({Key? key}) : super(key: key);
#override
State<Jobs> createState() => _JobsState();
}
class _JobsState extends State<Jobs> {
final adManager = AdManager();
// We will fetch data from this Rest api
final _baseUrl = 'api';
// At the beginning, we fetch the first 20 posts
int _page = 0;
// you can change this value to fetch more or less posts per page (10, 15, 5, etc)
final int _limit = 15;
// There is next page or not
bool _hasNextPage = true;
// Used to display loading indicators when _firstLoad function is running
bool _isFirstLoadRunning = false;
// Used to display loading indicators when _loadMore function is running
bool _isLoadMoreRunning = false;
// This holds the posts fetched from the server
List _posts = [];
// This function will be called when the app launches (see the initState function)
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
final res = await http.get(Uri.parse("$_baseUrl?$_page&$_limit"));
setState(() {
_posts = json.decode(res.body);
});
} catch (err) {
if (kDebugMode) {
print('Something went wrong');
}
}
setState(() {
_isFirstLoadRunning = false;
});
}
// This function will be triggered whenver the user scroll
// to near the bottom of the list view
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false &&
_controller.position.extentAfter < 300) {
setState(() {
_isLoadMoreRunning = true; // Display a progress indicator at the bottom
});
_page += 1; // Increase _page by 1
try {
final res = await http.get(Uri.parse("$_baseUrl?$_page&$_limit"));
final List fetchedPosts = json.decode(res.body);
if (fetchedPosts.isNotEmpty) {
if (!fetchedPosts.contains(fetchedPosts)) {
setState(() {
_posts.addAll(fetchedPosts);
});
}
} else {
// This means there is no more data
// and therefore, we will not send another GET request
setState(() {
_hasNextPage = false;
});
}
} catch (err) {
if (kDebugMode) {
print('Something went wrong!');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
// The controller for the ListView
late ScrollController _controller;
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController()..addListener(_loadMore);
adManager.addAds(true, true, true);
adManager.showInterstitial();
}
#override
void dispose() {
_controller.removeListener(_loadMore);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: headerNav(title: "Sarkari Naukri"),
body: _isFirstLoadRunning
? ListView.separated(
itemCount: 5,
itemBuilder: (context, index) => const NewsCardSkelton(),
separatorBuilder: (context, index) =>
const SizedBox(height: defaultPadding),
)
: Column(
children: [
Expanded(
child: ListView.separated(
controller: _controller,
itemCount: _posts.length,
itemBuilder: (_, index) => JobCard(
onPress: () {
FlutterWebBrowser.openWebPage(
url: _posts[index]['moredetils'],
safariVCOptions: const SafariViewControllerOptions(
barCollapsingEnabled: true,
preferredBarTintColor: Colors.green,
preferredControlTintColor: Colors.amber,
dismissButtonStyle:
SafariViewControllerDismissButtonStyle.close,
modalPresentationCapturesStatusBarAppearance: true,
),
);
},
image: _posts[index]['imagelink'],
state: _posts[index]['state'],
title: _posts[index]['title'],
subtitle: _posts[index]['totalpost'].toString(),
),
separatorBuilder: (BuildContext context, int index) {
if ((index + 1) % 4 == 0) {
return Container(
width: adManager.getBannerAd()?.size.width.toDouble(),
height:
adManager.getBannerAd()?.size.height.toDouble(),
child: AdWidget(ad: adManager.getBannerAd()!),
);
}
return const SizedBox();
},
),
),
// when the _loadMore function is running
if (_isLoadMoreRunning == true)
const Padding(
padding: EdgeInsets.only(top: 10, bottom: 40),
child: Center(
child: CircularProgressIndicator(),
),
),
// When nothing else to load
if (_hasNextPage == false)
Container(
padding: const EdgeInsets.only(top: 30, bottom: 40),
color: Colors.amber,
child: const Center(
child: Text('No more jobs'),
),
),
],
),
);
}
}
class NewsCardSkelton extends StatelessWidget {
const NewsCardSkelton({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Skeleton(height: 120, width: 120),
const SizedBox(width: defaultPadding),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Skeleton(width: 80),
const SizedBox(height: defaultPadding / 2),
const Skeleton(),
const SizedBox(height: defaultPadding / 2),
const Skeleton(),
const SizedBox(height: defaultPadding / 2),
Row(
children: const [
Expanded(
child: Skeleton(),
),
SizedBox(width: defaultPadding),
Expanded(
child: Skeleton(),
),
],
)
],
),
)
],
);
}
}
Thank you.
This code looks good to me!!. Maybe you should clear _posts in loadMore(). For example:
// This function will be triggered whenver the user scroll
// to near the bottom of the list view
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false &&
_controller.position.extentAfter < 300) {
setState(() {
_isLoadMoreRunning = true; // Display a progress indicator at the bottom
});
_page += 1; // Increase _page by 1
try {
final res = await http.get(Uri.parse("$_baseUrl?$_page&$_limit"));
final List fetchedPosts = json.decode(res.body);
if (fetchedPosts.isNotEmpty) {
if (!fetchedPosts.contains(fetchedPosts)) {
setState(() {
// added this line
_posts.clear();
_posts.addAll(fetchedPosts);
});
}
} else {
// This means there is no more data
// and therefore, we will not send another GET request
setState(() {
_hasNextPage = false;
});
}
} catch (err) {
if (kDebugMode) {
print('Something went wrong!');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
I can see your URL pattern look like trying to use GET URL parameters like this $_baseUrl?$_page&$_limit, I think it supposed to be something like $_baseUrl?page=$_page&limit=$_limit

Unhandled Exception: setState() called after dispose() - due to modal dissmissed

I have a modalBottomSheet. On it, I am displaying several widgets, especially an audioPlayer.
I have find out that when I press the play button, the audio file is playing, so far so good, but if I tap outside the modalBottomSheet, the modal is dismissed. I am OK to get the modal dismissed. But my problem is that when it is dismissed, the player which is running, is generating an exception.
Unhandled Exception: setState() called after dispose()
I do not want to make the Modal not dissmisible.
Please, can you advise? Many thanks.
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers/audioplayers_api.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gtd_official_sharped_focused/Reusable%20Widget/Player_Audio/widgets/play_pause_button.dart';
class AudioPlayerWidget extends StatefulWidget {
final String url;
final bool isAsset;
final Duration currentTime;
final Duration totalTime;
final ValueChanged<Duration> onSeekBarMoved;
const AudioPlayerWidget({
Key key,
this.url,
this.isAsset = false,
this.currentTime,
this.totalTime,
this.onSeekBarMoved,
}) : super(key: key);
#override
_AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
}
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
AudioPlayer _audioPlayer;
AudioCache _audioCache;
//variables for slider
Duration _duration = new Duration();
Duration _position = new Duration();
PlayerState _playerState = PlayerState.STOPPED;
bool get _isPlaying => _playerState == PlayerState.PLAYING;
bool get _isLocal => !widget.url.contains('https');
#override
void initState() {
_audioPlayer = AudioPlayer(mode: PlayerMode.MEDIA_PLAYER);
_audioCache = AudioCache(fixedPlayer: _audioPlayer);
_audioPlayer.onDurationChanged.listen((d) {setState(() {
_duration = d;
});});
_audioPlayer.onAudioPositionChanged.listen((p) {setState((){
_position = p;
});});
_audioPlayer.onPlayerCompletion.listen((event) {
setState(() {
_position = Duration(seconds: 0);
_playerState = PlayerState.STOPPED;
});
});
_audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.STOPPED;
});
});
super.initState();
}
#override
void dispose() {
_audioPlayer.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left:18.0),
child: Text(_position.toString().split('.')[0],style:TextStyle(fontSize: 16)),
),
Padding(
padding: const EdgeInsets.only(right:18.0),
child: Text(_duration.toString().split('.')[0],style:TextStyle(fontSize: 16)),
),
],),
_buildSliderBar(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buttonBackWard10Seconds(),
PlayPauseButton(
isPlaying: _isPlaying,
onPlay: () => _playPause()
),
buttonForward10Seconds(),
//Do not delete iconButton below => for reference
/* IconButton(
onPressed: () => _stop(),
icon: Icon(
Icons.stop,
size: 40,
color: Colors.red,
),
),*/
],
),
],
);
}
//########################################################
_playPause() async {
if (_playerState == PlayerState.PLAYING) {
final playerResult = await _audioPlayer.pause();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PAUSED;
});
}
} else if (_playerState == PlayerState.PAUSED) {
final playerResult = await _audioPlayer.resume();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
} else {
if (widget.isAsset) {
_audioPlayer = await _audioCache.play(widget.url);
setState(() {
_playerState = PlayerState.PLAYING;
});
} else {
final playerResult = await _audioPlayer.play(widget.url, isLocal: _isLocal);
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
}
}
}
void changeToSecond(int second){
Duration newDuration = Duration(seconds:second);
_audioPlayer.seek(newDuration);
}
_stop() async {
final playerResult = await _audioPlayer.stop();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.STOPPED;
});
}
}
//###############################################################
Slider _buildSliderBar(BuildContext context) {
return Slider(
value: _position.inSeconds.toDouble(),
min: 0.0,
max: _duration.inSeconds.toDouble(), //_sliderValue,
activeColor: Colors.red,
inactiveColor: Colors.grey,
onChanged: (double value) {
setState(() {
changeToSecond(value.toInt());
value=value;
});
},
);
}
Widget buttonBackWard10Seconds(){
return IconButton( icon: Icon(CupertinoIcons.gobackward_10),
iconSize: 40,
color: Colors.black,
onPressed: (){
_position = _position - Duration(seconds:10);
if (_position < Duration(seconds:0)) {
_audioPlayer.seek(Duration(seconds: 0));
}
else {
_audioPlayer.seek(_position);
}});
}
Widget buttonForward10Seconds(){
return IconButton( icon:Icon( CupertinoIcons.goforward_10),
iconSize: 40,
color: Colors.black,
onPressed: (){
_position = _position + Duration(seconds:10);
if (_duration >_position) {
_audioPlayer.seek(_position);
}
else if (_duration <_position) {
_audioPlayer.seek(_duration);
}
}
);
}
}
import 'package:flutter/material.dart';
import 'package:gtd_official_sharped_focused/Reusable%20Widget/Player_Audio/widgets/audio_player_widget.dart';
import 'package:gtd_official_sharped_focused/Services/extract_file_Name_url/extract_file_name_url.dart';
Widget modalBottomPlayAudio (context,String urlToPlay){
showModalBottomSheet(
context: context,
//background color for modal bottom screen
backgroundColor: Colors.white,
//elevates modal bottom screen
elevation: 10,
// gives rounded corner to modal bottom screen
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
builder: (BuildContext context) {
// UDE : SizedBox instead of Container for whitespaces
return SizedBox(
height: 350,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
/*Padding(
padding: const EdgeInsets.all(28.0),
),*/
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left:18.0),
child: Text(getFileNameFromURL(urlToPlay),
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
),
],
),
SizedBox(height: 60,),
AudioPlayerWidget(url:urlToPlay),
],
),
),
);
},
);
}
you could change
setState(()=>{
...
})
to
if(mounted)(
setState(()=>{
...
})
)
Which ensures setState is called only when the widget is mounted on the screen.

PageView is consuming all gestures with embedded webview in flutter

I have an embedded web view that is nested alongside a pageview builder which controls my three js animations by sending javascript to the embedded web view.
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_museum/app/data/bloc/artifacts.dart';
import 'package:flutter_museum/app/domain/models/artifact_model.dart';
import 'package:flutter_museum/app/ui/discover/widgets/animatedCardTutorial.dart';
import 'package:flutter_museum/app/ui/discover/widgets/focusPointList.dart';
import 'package:flutter_museum/app/ui/discover/widgets/questionMarkButton.dart';
import 'package:flutter_museum/app/ui/discover/widgets/threeJSViewer.dart';
import 'package:flutter_museum/app/ui/widgets/assetOrCachedNetworkImage.dart';
import 'package:flutter_museum/app/ui/widgets/circularBackButton.dart';
import 'package:flutter_museum/app/ui/widgets/simpleVideo.dart';
import 'package:flutter_museum/app/ui/widgets/videoPlayerControllers.dart';
double deviceHeight = 0.0;
double deviceWidth = 0.0;
class ArtifactView extends StatefulWidget {
ArtifactView({
Key? key,
}) : super(key: key);
#override
State<ArtifactView> createState() => _ArtifactViewState();
}
class _ArtifactViewState extends State<ArtifactView> {
bool _fullscreen = false;
AbstractVideoPlayerController? videoPlayerController;
Timer? _restoreUiTimer;
int? startUpCount;
bool shouldShowTutorial = false;
void startFullScreen() {
if (!_fullscreen) {
setState(() {
_fullscreen = true;
_restoreUiTimer?.cancel();
_restoreUiTimer = Timer(Duration(milliseconds: 1500), endFullScreen);
});
}
}
void endFullScreen() {
setState(() {
_fullscreen = false;
});
}
void hideTutorial() {
setState(() {
shouldShowTutorial = false;
});
}
#override
void initState() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
super.initState();
}
#override
void dispose() {
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
videoPlayerController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// hide status bar when displaying fullscreen artifact. (otherwise show status bar)
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: _fullscreen ? [] : SystemUiOverlay.values);
return BlocConsumer<ArtifactsBloc, ArtifactsState>(
listener: (context, state) {},
builder: (context, state) {
Artifact artifact = state.hasArtifact ? state.currentArtifact : Artifact.empty;
//List<FocusPoint> focusPoints = state.hasArtifact ? state.focusPoints : [];
//String modelFilePath = state.modelPath;
//bool modelIsAsset = isAssetUrl(modelFilePath);
if (videoPlayerController == null && artifact.uuid == 'ironman_artifact') {
// EasterEgg // TODO - have Kodi add the polka.mp3 file to our CDN and reference that below instead of userbob.com
videoPlayerController = createVideoPlayerController('https://userbob.com/motb/polka.mp3', useChewie: true);
videoPlayerController?.initialize();
videoPlayerController?.play();
}
// FIXME - trying to always display background sized to device's dimensions so that the background
// doesn't shift when switching to full-screen mode. The code below will prevent the background from
// shifting after the first time we've gone into full-screen mode. However, the very first time
// their will be a slight shift.
//!FIXME - We should look into the overlfow widdget to let the scaffold know we intend to let this widget be larger than the screen.
var windowSize = MediaQuery.of(context).size;
if (windowSize.height > deviceHeight) deviceHeight = windowSize.height;
if (windowSize.width > deviceWidth) deviceWidth = windowSize.width;
return Center(
child: Container(
height: deviceHeight,
width: deviceWidth,
child: Stack(
children: [
if (videoPlayerController != null)
SimpleVideo(
placeholderImage: 'asset://images/discover/washingtonrevelations.webp',
videoPlayerController: videoPlayerController!,
),
if (artifact.backgroundUrl.isNotEmpty)
Container(
height: deviceHeight,
width: deviceWidth,
child: AssetOrCachedNetworkImage(imageUrl: artifact.backgroundUrl, fit: BoxFit.cover),
),
Container(
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 0.9,
colors: [
Colors.transparent,
Colors.transparent,
Colors.black38,
],
),
),
height: deviceHeight,
width: deviceWidth,
),
Listener(
onPointerDown: (PointerDownEvent e) {
startFullScreen();
},
child: GestureDetector(
onPanEnd: ((details) {
print('ended');
}),
child: ThreeJSViewer(
modelFilePath: state.modelPath,
initScale: artifact.cameraZoom.toInt(),
autoRotateSpeed: 2,
state: state,
),
),
),
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.only(top: 35),
child: CircularBackButton(
outterPadding: EdgeInsets.fromLTRB(10, 10, 0, 0),
onTap: () {
AutoRouter.of(context).pop();
},
),
),
),
AnimatedPositioned(
bottom: 60 - (_fullscreen ? 250 : 0.0),
duration: Duration(milliseconds: 300),
child: GestureDetector(
child: FocusPointList(
state: state,
),
),
),
],
),
),
);
},
);
}
}
finally here is my WebView plus widget
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_museum/app/data/bloc/artifact/artifacts_state.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:webview_flutter_plus/webview_flutter_plus.dart';
// ignore: must_be_immutable
class ThreeJSViewer extends StatefulWidget {
final String? modelFilePath;
final String? modelURLPath;
final double? autoRotateSpeed;
final int initScale;
final ArtifactsState state;
final bool? debug;
final void Function(PointerMoveEvent)? onPointerMove;
ThreeJSViewer({
Key? key,
this.modelFilePath,
this.onPointerMove,
this.modelURLPath,
required this.initScale,
this.autoRotateSpeed,
required this.state,
this.debug,
}) : super(key: key);
#override
State<ThreeJSViewer> createState() => _ThreeJSViewerState();
}
class _ThreeJSViewerState extends State<ThreeJSViewer> {
int progress = 0;
WebViewPlusController? controller;
bool isRendered = false;
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: (context, snapshot) {
if (snapshot.data == true) {
return Stack(
children: [
if (progress < 100 && isRendered == true)
Align(
alignment: Alignment.center,
child: Container(
height: 100,
width: 100,
child: CircularProgressIndicator(
value: progress.toDouble(),
),
),
),
WebViewPlus(
initialCookies: [],
gestureNavigationEnabled: true,
zoomEnabled: false,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
new Factory<OneSequenceGestureRecognizer>(
() {
print('GestureRecognizer created');
return new EagerGestureRecognizer();
},
),
].toSet(),
backgroundColor: Colors.transparent,
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: ({
JavascriptChannel(
name: 'JavascriptChannel',
onMessageReceived: (message) {
if (message == "200") {
setState(() {
isRendered = true;
});
}
log(message.message);
},
)
}),
onProgress: (_progress) {
setState(() {
progress = _progress;
});
},
onWebViewCreated: (_controller) {
controller = _controller;
_controller.loadUrl("assets/renderer/index.html");
},
onPageFinished: (value) {
Future.delayed(Duration(milliseconds: 100), () {
this.widget.state.controller = controller?.webViewController;
this.widget.state.controller!.runJavascript(
'init("${this.widget.modelFilePath ?? this.widget.modelURLPath}", ${this.widget.initScale},${this.widget.initScale},${this.widget.initScale}, ${this.widget.autoRotateSpeed ?? 2}, ${this.widget.debug ?? false})',
);
});
},
onPageStarted: (value) {
log(value);
},
onWebResourceError: (value) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error loading model. Please make sure you have internet connection.'),
),
);
log(value.description);
},
),
],
);
} else {
return Center(
child: Container(
child: DefaultTextStyle(
style: TextStyle(color: Colors.white, fontSize: 25),
child: Text(
"no internet connection",
),
),
),
);
}
},
future: InternetConnectionChecker().hasConnection,
);
}
}
My WebView is able to accept all gesture events when I initially rotate or zoom the three js model, but when I swipe a card on the pageview builder it consumes whatever gesture event for the duration of the widget lifetime.
finally here are the pageview cards
import 'package:flutter/material.dart';
import 'package:flutter_museum/app/data/bloc/artifact/artifacts_state.dart';
import 'package:flutter_museum/app/domain/models.dart';
import 'package:flutter_museum/app/ui/discover/views/focusPointView.dart';
import 'package:flutter_museum/app/ui/discover/widgets/focusPointCard.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_museum/app/data/bloc/artifacts.dart';
class FocusPointList extends StatefulWidget {
final ArtifactsState state;
const FocusPointList({Key? key, required this.state}) : super(key: key);
#override
State<FocusPointList> createState() => _FocusPointListState();
}
class _FocusPointListState extends State<FocusPointList> {
#override
Widget build(BuildContext context) {
int _centeredFocusPointIndex = 0;
ArtifactsState _state = this.widget.state;
List<FocusPoint> focusPoints = _state.hasArtifact ? _state.focusPoints : [];
List artifactFocusPointsList = focusPoints
.map(
(fp) => FocusPointCard(
publisher: fp.publisher ?? '',
subtitle: fp.subtitle ?? '',
title: fp.title,
imageUrl: fp.thumbnailImageUrl,
externalLink: fp.externalLink,
cardOnTap: () => _handleTapOnCard(context: context, focusPoint: fp),
),
)
.toList();
return Container(
height: 160,
width: MediaQuery.of(context).size.width,
child: PageView.builder(
itemCount: artifactFocusPointsList.length,
controller: PageController(viewportFraction: 0.75),
onPageChanged: (int index) {
setState(() {
_centeredFocusPointIndex = index;
FocusPoint afp = focusPoints[index];
bool isPointsEmpty = afp.x + afp.y + afp.z != 0;
if (index < focusPoints.length - 1 && isPointsEmpty) {
_centeredFocusPointIndex = index;
_state.controller?.runJavascript('tweenCamera(${afp.x}, ${afp.y}, ${afp.z}, 2000);');
}
});
},
itemBuilder: (_, index) {
return Transform.scale(
scale: index == _centeredFocusPointIndex ? 1 : 0.9,
child: artifactFocusPointsList[index],
);
},
),
);
}
void _handleTapOnCard({required BuildContext context, required FocusPoint focusPoint}) {
if (focusPoint.externalLink != null) {
launch(focusPoint.externalLink!);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FocusPointView(
title: focusPoint.title,
subtitle: focusPoint.subtitle ?? '',
publisher: focusPoint.publisher ?? '',
imageUrl: focusPoint.detailImageUrl ?? '',
description: focusPoint.description ?? '',
),
),
);
}
}
}

How do I pass data from a stateful widget to state ensuring widget data is available?

I'm passing data from a stateful widget, and I access it like in this example (widget.variable)
https://stackoverflow.com/a/50818870/806009
However, occasionally it throws an error (about 1 in 20) for a line in this comparison
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The method '>' was called on null.
Receiver: null
Tried calling: >(10)
if (widget.x > 10){...}
It seems that widget.x is not known at this time. Should I use initState to ensure that value is "ready"?
Such as
#override
void initState() {
super.initState();
this.x = widget.x
}
Full Code
class PlayerSelection extends StatefulWidget {
final int teamId;
final int leagueId;
final League.League league;
final String className;
List<PlayerObj> playersListOfClass;
final int index;
final int remaining;
PlayerSelection({Key key, #required this.teamId, this.leagueId, this.league, this.className, this.playersListOfClass, this.index, this.remaining}) : super(key: key);
#override
_PlayerSelection createState() => _PlayerSelection();
}
class _PlayerSelection extends State<PlayerSelection> {
var playerWidgets = <Widget>[];
List<PlayerObj> selectedPlayers = [];
List<PlayerObj> playerList = [];
PlayerObj draftedPlayer;
List<PlayerClass> playerClassList = [];
bool _isFavorited = false;
Modal modal = new Modal();
int lineupWeek = 0;
int lineupSize = 0;
String intervalLabel = '';
bool _is_full = false;
bool _is_locked = false;
int remaining = 0;
var currentColor = Colors.black;
var rejectedPlayerId;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: globals.MyAppBar(
leading: IconButton(icon: Icon(Icons.close),
onPressed: (){
Navigator.pop(context, {'player': null,'index': this.widget.index});
}), // go to league route,),
title: Text(widget.className)
),
body: Container(
child: Column(children: [Expanded(child: ListView(children: _getLineup(this.widget.playersListOfClass))),]),
),
);
}
List<Widget>_getLineup(playerList) {
List<Widget> widgets = [];
var index = 0;
playerList.forEach((player) =>
widgets.add(
Padding(
padding: const EdgeInsets.symmetric(horizontal:16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.grey.withOpacity(0.3)),
),
),
child: new ListTile(
leading: GestureDetector(
onTap: (){
if(!_is_full) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Players.PlayerDetail(playerId: player.playerId,
leagueId: widget.leagueId,
playerName: player.playerName,
playerBio: player.playerBio)),
);
}
},
child: ClipOval(
child: _playerPicWidget(player)
),
),
title: GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Players.PlayerDetail(playerId: player.playerId,
leagueId: widget.leagueId,
playerName: player.playerName,
playerBio: player.playerBio)),
);
},
child: _playerNameWidget(player)
),
trailing: _trailingWidget(player),
onTap: () => player.playerPrice <= widget.remaining ? Navigator.pop(context, {'player': player,'index': this.widget.index}) : null,
// return player back
),
),
)
)
);
return widgets;
}
Widget _playerNameWidget(player){
if(this._is_full && !this.selectedPlayers.contains(player)){ //show disabled selection
return Opacity(opacity:.25, child:Text("${player.playerName}"));
}
else {
if(this.widget.league.hasBudget){ // can afford player, not full
if(player.playerPrice <= widget.remaining || this.selectedPlayers.contains(player)){
return Text("${player.playerName}");
}
else { // can't afford player, not full
return Opacity(opacity:.25, child:Text("${player.playerName}"));
}
}
else { // slot still open
return Text("${player.playerName}");
}
}
}
Widget _playerPicWidget(player){
if(player == this.draftedPlayer){
return Opacity(opacity: .25, child: Image.network('${player.playerImageUrl}',
fit: BoxFit.scaleDown,
height: 45,
width: 45,
));
}
else {
if(player.playerPrice <= widget.remaining || this.selectedPlayers.contains(player)){
return Image.network('${player.playerImageUrl}',
fit: BoxFit.scaleDown,
height: 45,
width: 45,
);
}
}
}
Widget _trailingWidget(player){
List<Widget> tWidgets;
double playerOpacity = 1;
if(player.playerPrice > widget.remaining){
playerOpacity = .25;
}
tWidgets = [
Padding(padding: const EdgeInsets.symmetric(horizontal:10.0),
child: Opacity(opacity:playerOpacity, child:Text("\$${globals.commaFormat.format(player.playerPrice)}")),
), Opacity(opacity: playerOpacity, child: Icon(Icons.add))];
return Row(mainAxisSize: MainAxisSize.min, children: tWidgets);
}
}
The issue was a data issue unrelated to the passing of data.

Flutter : How to get information about which picture in the grid has been selected?

How to get information about which picture in the grid has been selected in flutter. Please check my code!
I really need an answer to this. Please help me. I am looking forward to hearing from all of you.
Getting image and put it into a grid.
ImageEvalation.dart
class ImageEvaluation extends StatefulWidget {
#override
_ImageEvaluationState createState() => _ImageEvaluationState();
}
class _ImageEvaluationState extends State<ImageEvaluation> {
File _selectedFile;
bool _inProcess = false;
String colorCode = '#33695d';
getImage(ImageSource source, BuildContext context) async {
this.setState(() {
_inProcess = true;
});
// File image = await ImagePicker.pickImage(source: source);
final _picker = ImagePicker();
PickedFile image = await _picker.getImage(source: source);
if (image != null) {
// Remove crop attribute if we don't want to resize the image
File cropped = await ImageCropper.cropImage(
sourcePath: image.path,
aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1),
compressQuality: 100, // 100 means no compression
maxWidth: 700,
maxHeight: 700,
compressFormat: ImageCompressFormat.jpg,
androidUiSettings: AndroidUiSettings(
toolbarColor: HexColor(colorCode),
toolbarTitle: "RPS Cropper",
statusBarColor: HexColor(colorCode),
backgroundColor: Colors.white,
//toolbarWidgetColor: HexColor(colorCode),
activeControlsWidgetColor: HexColor(colorCode),
//dimmedLayerColor: HexColor(colorCode),
cropFrameColor: HexColor(colorCode),
cropGridColor: HexColor(colorCode),
),
);
this.setState(() {
_selectedFile = cropped;
_inProcess = false;
//_showDelete = true;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UploadScreen(
image: _selectedFile,
),
),
);
} else {
this.setState(() {
_inProcess = false;
});
}
}
#override
Widget build(BuildContext context) {
return _inProcess
? Loading()
: Scaffold(
body: StreamProvider<List<ImageProperty>>.value(
value: User_DatabaseService().pictureData,
child: SingleChildScrollView(
child: Center(
child: Column(
children: <Widget>[
Text('GridView'),
PictureLinkGrid(),
Text('Image Evulation'),
MaterialButton(
onPressed: () {
getImage(ImageSource.camera, context);
},
color: Colors.deepOrange,
child: Text(
'NEXT',
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
),
);
}
}
Make a grid for my images.
PictureGrid.dart
class PictureLinkGrid extends StatefulWidget {
#override
_PictureLinkGridState createState() => _PictureLinkGridState();
}
class _PictureLinkGridState extends State<PictureLinkGrid> {
#override
Widget build(BuildContext context) {
final pictureData = Provider.of<List<ImageProperty>>(context) ?? [];
final neededPicture = [];
final demoPicture = [];
int count = 0;
// get Demo Picture
pictureData.forEach((picture) {
if (picture.title.contains('demo')) {
demoPicture.add(picture);
}
});
// get Needed Picture
pictureData.forEach((picture) {
if (picture.display_count < 10 && !picture.title.contains('demo')) {
print('${picture.title} is NOT null');
neededPicture.add(picture);
} else {
print('${picture.title} is null');
}
});
// fill in the empty picture
count = 0;
while (neededPicture.length < 9) {
neededPicture.add(demoPicture[count]);
count++;
}
return GridView.builder(
//itemCount: neededPicture.length,
itemCount: neededPicture.length,
shrinkWrap: true,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
print(
'Picture title in picturelink grid: ${neededPicture[index].title}');
return TouchableWebImageCard(imagePath: neededPicture[index].url);
});
}
}
Make ImageCard which can be clicked and unchecked.
TouchableWebImageCard.dart
class TouchableWebImageCard extends StatefulWidget {
String imagePath;
TouchableWebImageCard({#required this.imagePath});
//
#override
_TouchableWebImageCardState createState() =>
_TouchableWebImageCardState(imagePath);
}
class _TouchableWebImageCardState extends State<TouchableWebImageCard> {
// To pass parameters
double width;
double height;
String imagePath;
_TouchableWebImageCardState(this.imagePath);
//
bool isChecked = false;
double sigmaX = 0.0;
double sigmaY = 0.0;
double showBorder = 0;
//
checkIcon() {
return isChecked
? Center(
child: Icon(
Icons.check_circle,
color: Colors.white,
size: 50,
),
)
: Container();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Center(
child: SpinKitCircle(
color: HexColor('#33695d'),
size: 50,
),
),
Center(
child: InkWell(
child: Stack(
children: <Widget>[
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: imagePath,
),
checkIcon(),
],
),
onTap: () {
print('Image Path: $imagePath');
if (isChecked != true) {
setState(
() {
showBorder = 4.0;
isChecked = !isChecked;
},
);
} else {
setState(
() {
showBorder = 0.0;
isChecked = !isChecked;
},
);
}
},
),
),
],
);
}
}
In your TouchableWebImageCard constructor add another variable int _index;
So will know which image you have selected.
Also, the "right" way to make a constructor is:
class TouchableWebImageCard extends StatefulWidget {
TouchableWebImageCard({#required this.imagePath, this._index});
String imagePath;
int _index;