Flutter Camera startImageStream returning images of null - flutter

I'm trying to create a realtime poseNet demo app with the help of Flutter and its Camera package.
Displaying the camera preview works very well, but when I try to access the image stream via controller.startImageStream the program crashes. I've succeded in tracking the source of that bug down, and it seems to me that the incoming frames in the 'controller.startImageStream' return values of null. Why is this, and how can I possibly fix it?
The snippet looks like this:
controller.startImageStream((CameraImage img) {
controller.stopImageStream();
if (!isPaused) {
runPoseNetOnFrame(bytesList: img.planes.map((plane) {
return plane.bytes;
}).toList(),
imageHeight: img.height,
imageWidth: img.width,
numResults: 1).then((recognitions) {
setState(() {
photo = img;
_recognitions = recognitions;
imageHeight = img.height;
imageWidth = img.width;
});
});
}
});
And it's the variable, img that's for every iteration composed of nothing but type null when it really should contain a frame of type CameraImage
While the app crashes I get this error in my console:
E/flutter (26077): [ERROR:flutter/shell/common/shell.cc(178)] Dart Error: Unhandled exception:
E/flutter (26077): NoSuchMethodError: The getter 'isActive' was called on null.
E/flutter (26077): Receiver: null
E/flutter (26077): Tried calling: isActive
Here's my code for the entire page:
import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';
import 'package:flutter/services.dart';
import 'dart:typed_data';
import 'package:camera/camera.dart';
class CoachPage extends StatefulWidget {
#override
_CoachPageState createState() {
return _CoachPageState();
}
}
class _CoachPageState extends State<CoachPage> {
bool isPaused = true;
CameraController controller;
List cameras;
int selectedCameraIdx;
String imagePath;
static const MethodChannel _channel = const MethodChannel('tflite');
String model;
List<dynamic> _recognitions;
int imageHeight;
int imageWidth;
CameraImage photo;
#override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
cameras = availableCameras;
if (cameras.length > 0) {
setState(() {
selectedCameraIdx = 0;
});
_initCameraController(cameras[selectedCameraIdx]);
}
else{
print("No camera available");
}
}).catchError((err) {
print('Error: $err.code\nError Message: $err.message');
});
}
void _initCameraController(CameraDescription cameraDescription) {
controller = CameraController(cameraDescription, ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
controller.addListener(() {});
controller.startImageStream((CameraImage img) {
controller.stopImageStream();
if (!isPaused) {
runPoseNetOnFrame(bytesList: img.planes.map((plane) {
return plane.bytes;
}).toList(),
imageHeight: img.height,
imageWidth: img.width,
numResults: 1).then((recognitions) {
setState(() {
photo = img;
_recognitions = recognitions;
imageHeight = img.height;
imageWidth = img.width;
});
});
}
});
setState(() {});
});}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: isPaused ? _cameraPreviewWidget()
: _renderKeypoints(),
),
SizedBox(height: 10.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_cameraTogglesRowWidget(),
_captureControlRowWidget(context),
Spacer(),
Text(photo.toString())
],
),
SizedBox(height: 20.0)
],
),
),
),
);
}
/// Display Camera preview.
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Loading',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
);
}
/// Display the control bar with buttons to take pictures
Widget _captureControlRowWidget(context) {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
FloatingActionButton(
child:(isPaused) ? Icon(Icons.play_arrow)
: Icon(Icons.pause),
backgroundColor: Colors.blueGrey,
onPressed: () {
setState(() {
isPaused = !isPaused;
});
})
],
),
),
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraTogglesRowWidget() {
if (cameras == null || cameras.isEmpty) {
return Spacer();
}
CameraDescription selectedCamera = cameras[selectedCameraIdx];
CameraLensDirection lensDirection = selectedCamera.lensDirection;
return Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: FlatButton.icon(
onPressed: _onSwitchCamera,
icon: Icon(_getCameraLensIcon(lensDirection)),
label: Text(
"${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1)}")),
),
);
}
IconData _getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return Icons.camera_rear;
case CameraLensDirection.front:
return Icons.camera_front;
case CameraLensDirection.external:
return Icons.camera;
default:
return Icons.device_unknown;
}
}
void _onSwitchCamera() {
selectedCameraIdx =
selectedCameraIdx < cameras.length - 1 ? selectedCameraIdx + 1 : 0;
CameraDescription selectedCamera = cameras[selectedCameraIdx];
_initCameraController(selectedCamera);
}
void _showCameraException(CameraException e) {
String errorText = 'Error: ${e.code}\nError Message: ${e.description}';
print(errorText);
print('Error: ${e.code}\n${e.description}');
}
Future loadModel() async {
Tflite.close();
String res = await Tflite.loadModel(
model: "assets/posenet_mv1_075_float_from_checkpoints.tflite");
model = res;
}
Future<List> runPoseNetOnFrame(
{#required List<Uint8List> bytesList,
int imageHeight = 1280,
int imageWidth = 720,
double imageMean = 127.5,
double imageStd = 127.5,
int rotation: 90, // Android only
int numResults = 1,
double threshold = 0.5,
int nmsRadius = 20,
bool asynch = true}) async {
return await _channel.invokeMethod(
'runPoseNetOnFrame',
{
"bytesList": bytesList,
"imageHeight": imageHeight,
"imageWidth": imageWidth,
"imageMean": imageMean,
"imageStd": imageStd,
"rotation": rotation,
"numResults": numResults,
"threshold": threshold,
"nmsRadius": nmsRadius,
"asynch": asynch,
},
);
}
List<Widget> _renderKeypoints() {
var lists = <Widget>[];
_recognitions.forEach((re) {
var list = re["keypoints"].values.map<Widget>((k) {
var x = k["x"];
var y = k["y"];
return Positioned(
left: x - 6,
top: y - 6,
width: 100,
height: 12,
child: Container(
child: Text(
"● ${k["part"]}",
style: TextStyle(
color: Color.fromRGBO(37, 213, 253, 1.0),
fontSize: 12.0,
),
),
),
);
}).toList();
lists.addAll(list);
});
return lists;
}
}
Thanks in advance! ;)

Checking your code, the issue seems to be caused by calling controller.stopImageStream(); immediately after controller.startImageStream(). You may want to consider closing the Stream only when it's not in use, like in dispose() override method.

Related

Preventing barcode scanning using Ml-kit from triggering a function call twice

I'm using Google's ML-Kit barcode scanning plugin along with Flutter's default camera plugin for my project. From my testing, I can see that if I scan a QR code with the current implementation that navigates you to another screen, then navigate back, the whole navigation process is triggered again. Using logging, I've found that it's probably caused because the parseImageForQRCode() function is triggered very fast between _processCameraImage() calls made by the camera's image stream. This in turn doesn't give enough time to the parseImageForQRCode() function to update the _isBusy variable and basically lock the function like a mutex. Is there a recommended way for avoiding such race cases?
camera_screen.dart:
class CameraComponent extends StatefulWidget {
const CameraComponent({Key? key}) : super(key: key);
#override
State<CameraComponent> createState() => _CameraComponentState();
}
class _CameraComponentState extends State<CameraComponent>
with WidgetsBindingObserver {
CameraCubit get _cubit => context.read<CameraCubit>();
CameraController? _controller;
final BarcodeScanner _barcodeScanner = BarcodeScanner();
bool _canProcess = true;
bool _isBusy = false;
CameraDescription? _camera;
final ArtworkRepository _artworkRepository = getIt<ArtworkRepository>();
Future<CameraDescription> getCamera() async {
// Obtain a list of the available cameras on the device.
final List<CameraDescription> cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
return cameras.first;
}
void _navigateToArtworkDetailsById({required int artworkId}) {
context.navigator.pushNamed(
ArtworkViewScreen.path,
arguments: <String, dynamic>{
'artworkId': artworkId,
'source': ArtworkViewSourceScreen.QRScan.name
},
);
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_cubit.init();
startCameraSetup();
}
Future<void> startCameraSetup() async {
_canProcess = true;
_camera = await getCamera();
//todo:sp handle camera being null
if (_camera == null) {
log('camera was null!');
displayErrorDialog('Camera Error',
'Looks like the camera is busy or unavailable! Please try again later.');
return;
}
_controller = CameraController(
_camera!,
ResolutionPreset.high,
enableAudio: false,
);
_controller?.initialize().then((_) {
if (!mounted) {
return;
}
_controller?.startImageStream(_processCameraImage);
setState(() {});
});
}
Future _processCameraImage(CameraImage image) async {
final WriteBuffer allBytes = WriteBuffer();
for (final Plane plane in image.planes) {
allBytes.putUint8List(plane.bytes);
}
final Uint8List bytes = allBytes.done().buffer.asUint8List();
final Size imageSize =
Size(image.width.toDouble(), image.height.toDouble());
final InputImageRotation? imageRotation =
InputImageRotationValue.fromRawValue(_camera!.sensorOrientation);
if (imageRotation == null) {
return;
}
final InputImageFormat? inputImageFormat =
InputImageFormatValue.fromRawValue(image.format.raw);
if (inputImageFormat == null) {
return;
}
final List<InputImagePlaneMetadata> planeData = image.planes.map(
(Plane plane) {
return InputImagePlaneMetadata(
bytesPerRow: plane.bytesPerRow,
height: plane.height,
width: plane.width,
);
},
).toList();
final InputImageData inputImageData = InputImageData(
size: imageSize,
imageRotation: imageRotation,
inputImageFormat: inputImageFormat,
planeData: planeData,
);
final InputImage inputImage =
InputImage.fromBytes(bytes: bytes, inputImageData: inputImageData);
parseImageForQRCode(inputImage);
}
Future parseImageForQRCode(InputImage inputImage) async {
if (!_canProcess) {
return;
}
if (_isBusy) {
return;
}
setState(() {
_isBusy = true;
});
final List<Barcode> barcodes =
await _barcodeScanner.processImage(inputImage);
if (barcodes.isNotEmpty) {
final Barcode qrcode = barcodes[0];
if (qrcode.rawValue != null) {
try {
final int artworkId = int.parse(qrcode.rawValue!.split('_')[1]);
_controller!.stopImageStream();
Future.delayed(const Duration(milliseconds: 100), () async {
_navigateToArtworkDetailsById(
artworkId: artworkId,
);
await _artworkRepository.scanArtwork(id: artworkId);
});
} catch (e) {
print(e);
displayErrorDialog('Oops',
'Looks like something went wrong! Please make sure you\'re using a valid QR code & try again.');
}
}
}
_isBusy = false;
if (mounted) {
setState(() {});
}
}
void displayErrorDialog(String title, String msg) {
showDialog<void>(
context: context,
builder: (BuildContext ctx) => AlertDialog(
title: Text(
title,
textAlign: TextAlign.center,
),
content: Text(msg),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: const Text('Ok'),
),
],
),
);
}
#override
void dispose() {
cleanup();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Future cleanup() async {
_canProcess = false;
_isBusy = false;
_barcodeScanner.close();
if (_controller != null && _controller!.value.isInitialized) {
try {
await _controller?.stopImageStream();
} catch (e) {
print(e);
}
}
await _controller?.dispose();
_controller = null;
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
log(state.name);
// final CameraController? cameraController = _controller;
// // App state changed before we got the chance to initialize.
// if (cameraController == null || !cameraController.value.isInitialized) {
// return;
// }
if (state == AppLifecycleState.inactive) {
log('inactive being called');
cleanup();
} else if (state == AppLifecycleState.resumed) {
log('on resume called');
startCameraSetup();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<CameraCubit, CameraState>(
listener: (BuildContext context, CameraState state) {
state.maybeWhen(
initCamera: (List<CameraDescription> cameras) {},
orElse: () {},
);
},
builder: (BuildContext context, CameraState state) {
return state.maybeWhen(
initCamera: (_) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
'Take a picture',
style: montserratRegular15.copyWith(color: AppColors.black),
),
),
body: Stack(
children: [
if (_controller?.value.isInitialized == true)
CameraPreview(_controller!)
else
const Center(
child: CircularProgressIndicator(
color: AppColors.red,
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(
bottom: 130,
),
child: Text(
'Scan a QR code or take a photo of\nthe artwork',
textAlign: TextAlign.center,
style: montserratRegular15.copyWith(
color: AppColors.white,
),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 30),
child: GestureDetector(
onTap: () async {
// Ensure that the camera is initialized.
if (!_controller!.value.isInitialized) {
displayErrorDialog('Camera Error',
'Looks like the camera is busy or unavailable. Please try again later.');
return;
}
await _controller!.stopImageStream();
final XFile image =
await _controller!.takePicture();
final Uint8List bytes = await image.readAsBytes();
final String base64Image = base64Encode(bytes);
final int artworkId =
await _cubit.imageSearchByBase64(
base64Image: base64Image,
);
if (_cubit.state is ArtworkIdFound$) {
_navigateToArtworkDetailsById(
artworkId: artworkId,
);
_cubit.init();
}
},
child: Image.asset(
'assets/icons/shutter_icon.png',
height: 72,
width: 72,
),
),
),
),
Align(
child: Padding(
padding: const EdgeInsets.all(50),
child: Image.asset('assets/icons/b_logo_grey.png'),
),
),
],
),
);
},
loading: () => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Center(
child: CircularProgressIndicator(
color: AppColors.red,
),
),
SizedBox(
height: 10,
),
Text(
'Performing image recognition...',
style: montserratRegular15,
)
],
),
initial: () => const Center(
child: CircularProgressIndicator(
color: AppColors.red,
),
),
artworkIdError: () {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Failed to recognize artwork!',
style: montserratRegular15,
),
const SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 100),
child: AppButton(
onTap: () {
_cubit.init();
setState(() {});
},
text: 'Retry',
),
),
],
);
},
orElse: () {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Center(
child: CircularProgressIndicator(
color: AppColors.red,
),
),
SizedBox(
height: 10,
),
Text(
'Performing image recognition...',
style: montserratRegular15,
)
],
);
},
);
},
),
);
}
}

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.

The method 'findRenderObject' was called on null - With StackTrace

Hi I'm new to Flutter Dart.
In my project I get the excpetion: The method 'findRenderObject' was called on null. Receiver: null Tried calling: findRenderObject()
This happens when I'm firing the ElevatedButton in my main.dart file.
The function that will be fired is the startGame function in my play_field.dart file.
Anyone an idea why I get this exception when firing this button?
The StackTrace:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 PlayFieldState.startGame
package:woost_games/…/game/play_field.dart:74
#2 _TetrisState.build.<anonymous closure>
package:woost_games/main.dart:88
#3 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:991
#4 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:182
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#dbf10
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(332.0, 284.3)
finalLocalPosition: Offset(33.1, 7.9)
button: 1
sent tap down
See my code below:
Main.dart
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'widgets/block/block.dart';
import 'widgets/block/next_block.dart';
import 'widgets/game/play_field.dart';
import 'widgets/game/score_bar.dart';
void main() => runApp(
ChangeNotifierProvider(
create: (context) => Data(),
child: WoostGames()
)
);
class WoostGames extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return MaterialApp(home: Tetris());
}
}
class Tetris extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TetrisState();
}
class _TetrisState extends State<Tetris> {
GlobalKey<PlayFieldState> _playFieldKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Woost Tetris',
style: TextStyle(
color: Colors.black,
fontFamily: 'Neue Montreal',
fontSize: 25,
fontWeight: FontWeight.w700
)
),
centerTitle: true,
backgroundColor: Colors.yellow,
),
backgroundColor: Colors.yellow,
body: SafeArea(
child: Column(children: <Widget>[
ScoreBar(),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Flexible(
flex: 3,
child: Padding(
padding: EdgeInsets.fromLTRB(5, 0, 2.5, 5),
child: PlayField(key: _playFieldKey),
)
),
Flexible(
child: Padding(
padding: EdgeInsets.fromLTRB(2.5, 0, 5, 5),
child: Column(
children: <Widget>[
NextBlock(),
SizedBox(height: 30),
ElevatedButton(
child: Text(
Provider.of<Data>(context, listen: false).inGame
? 'End'
: 'Start',
style: TextStyle(
fontSize: 18,
color: Colors.yellow
)
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.black)
),
onPressed: () {
Provider.of<Data>(context, listen: false).inGame
? _playFieldKey.currentState.endGame()
: _playFieldKey.currentState.startGame();
}
)
]
)
)
),
],
),
)
])
)
);
}
}
class Data with ChangeNotifier {
int score = 0;
bool inGame = false;
Block nextBlock;
void setScore(score) {
this.score = score ;
notifyListeners();
}
void addScore(score) {
this.score += score;
notifyListeners();
}
void setInGame(inGame) {
this.inGame = inGame;
notifyListeners();
}
void setNextBlock(Block nextBlock) {
this.nextBlock = nextBlock;
notifyListeners();
}
Widget getNextBlockWidget() {
if (!inGame) return Container();
var width = nextBlock.width;
var height = nextBlock.height;
var color;
List<Widget> columns = [];
for (var y = 0; y < height; y++) {
List<Widget> rows = [];
for (var x = 0; x < width; ++ x) {
color = (nextBlock.subBlocks
.where((subBlock) => subBlock.x == x && subBlock.y == y)
.length > 0) ? nextBlock.color : Colors.transparent;
rows.add(Container(width: 12, height: 12, color: color));
}
columns.add(Row(
mainAxisAlignment: MainAxisAlignment.center,
children: rows
));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: columns,
);
}
}
play_field.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../main.dart';
import '../block/block.dart';
import '../block/blocks.dart';
import '../block/sub_block.dart';
enum Collision {
LANDED_ON_BOTTOM,
LANDED_ON_BLOCK,
HIT_WALL,
HIT_BLOCK,
NONE
}
const PLAY_FIELD_WIDTH = 10;
const PLAY_FIELD_HEIGHT = 20;
const REFRESH_RATE = 300;
const GAME_AREA_BORDER_WIDTH = 2.0;
const SUB_BLOCK_EDGE_WIDTH = 2.0;
class PlayField extends StatefulWidget {
PlayField({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => PlayFieldState();
}
class PlayFieldState extends State<PlayField> {
bool isGameOver = false;
double subBlockWidth;
Duration blockMovementDuration = Duration(milliseconds: REFRESH_RATE);
GlobalKey _playFieldAreaKey = GlobalKey();
BlockMovement action;
Block block;
Timer timer;
List<SubBlock> oldSubBlocks;
Block getNewBlock() {
int blockType = Random().nextInt(7);
int orientationIndex = Random().nextInt(4);
switch (blockType) {
case 0:
return IBlock(orientationIndex);
case 1:
return JBlock(orientationIndex);
case 2:
return LBlock(orientationIndex);
case 3:
return OBlock(orientationIndex);
case 4:
return TBlock(orientationIndex);
case 5:
return SBlock(orientationIndex);
case 6:
return ZBlock(orientationIndex);
default:
return null;
}
}
void startGame() {
Provider.of<Data>(context, listen: false).setInGame(true);
Provider.of<Data>(context, listen: false).setScore(0);
oldSubBlocks = [];
RenderBox renderBoxPlayField = _playFieldAreaKey.currentContext.findRenderObject();
subBlockWidth = (renderBoxPlayField.size.width - GAME_AREA_BORDER_WIDTH * 2) / PLAY_FIELD_WIDTH;
Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
block = getNewBlock();
timer = Timer.periodic(blockMovementDuration, onPlay);
}
void endGame() {
Provider.of<Data>(context, listen: false).setInGame(false);
timer.cancel();
}
void onPlay(Timer timer) {
var status = Collision.NONE;
setState(() {
if (action != null) {
if (!checkOnEdge(action)) {
block.move(action);
}
}
oldSubBlocks.forEach((oldSubBlock) {
block.subBlocks.forEach((subBlock) {
var x = block.x + subBlock.x;
var y = block.y + subBlock.y;
if (x == oldSubBlock.x && y == oldSubBlock.y) {
switch (action) {
case BlockMovement.LEFT:
block.move(BlockMovement.RIGHT);
break;
case BlockMovement.RIGHT:
block.move(BlockMovement.LEFT);
break;
case BlockMovement.ROTATE_CLOCKWISE:
block.move(BlockMovement.ROTATE_COUNTER_CLOCKWISE);
break;
default:
break;
}
}
});
});
if (!checkAtBottom()) {
if (!checkOnBlock()) {
block.move(BlockMovement.DOWN);
} else {
status = Collision.LANDED_ON_BLOCK;
}
} else {
status = Collision.LANDED_ON_BOTTOM;
}
if (status == Collision.LANDED_ON_BLOCK && block.y < 0) {
isGameOver = true;
endGame();
}
else if (status == Collision.LANDED_ON_BOTTOM
|| status == Collision.LANDED_ON_BLOCK) {
block.subBlocks.forEach((subBlock) {
subBlock.x += block.x;
subBlock.y += block.y;
oldSubBlocks.add(subBlock);
});
}
block = Provider.of<Data>(context, listen: false).nextBlock;
Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
});
action = null;
updateScore();
}
void updateScore() {
var combo = 1;
Map<int, int> rows = Map();
List<int> rowsToBeRemoved = [];
oldSubBlocks?.forEach((oldSubBlock) {
rows.update(oldSubBlock.y, (value) => ++value, ifAbsent: () => 1);
});
rows.forEach((rowNum, count) {
if (count == PLAY_FIELD_WIDTH) {
Provider.of<Data>(context, listen: false).setScore(combo++);
rowsToBeRemoved.add(rowNum);
}
});
if (rowsToBeRemoved.length > 0)
removeRows(rowsToBeRemoved);
}
void removeRows(List<int> rows) {
rows.sort();
rows.forEach((row) {
oldSubBlocks.removeWhere((oldSubBlock) => oldSubBlock.y == row);
oldSubBlocks.forEach((oldSubBlock) {
if (oldSubBlock.y < row)
++oldSubBlock.y;
});
});
}
bool checkAtBottom() {
return block.y + block.height == PLAY_FIELD_HEIGHT;
}
bool checkOnBlock() {
oldSubBlocks.forEach((oldSubBlock) {
block.subBlocks.forEach((subBlock) {
var x = block.x + subBlock.x;
var y = block.y + subBlock.y;
if (x == oldSubBlock.x && y + 1 == oldSubBlock.y)
return true;
});
});
return false;
}
bool checkOnEdge(BlockMovement action) {
return (action == BlockMovement.LEFT && block.x <= 0) ||
(action == BlockMovement.RIGHT && block.x + block.width >= PLAY_FIELD_WIDTH);
}
Widget getPositionedSquareContainer(Color color, int x, int y) {
return Positioned(
left: x * subBlockWidth,
top: y * subBlockWidth,
child: Container(
width: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
height: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
decoration: BoxDecoration(
color: color,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(2.5))
)
)
);
}
Widget drawBlock() {
if (block == null) return null;
List<Positioned> subBlocks = [];
block.subBlocks.forEch((subBlock) {
subBlocks.add(getPositionedSquareContainer(
subBlock.color, subBlock.x + block.x, subBlock.y + block.y
));
});
oldSubBlocks?.forEach((oldSubBlock) {
subBlocks.add(getPositionedSquareContainer(
oldSubBlock.color, oldSubBlock.x, oldSubBlock.y
));
});
if (isGameOver) {
subBlocks.add(getGameOverRect());
}
return Stack(children: subBlocks);
}
Widget getGameOverRect() {
return Positioned(
child: Container(
width: subBlockWidth * 8,
height: subBlockWidth * 3,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
color: Colors.black
),
child: Text(
'Game Over',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.yellow
)
)
),
left: subBlockWidth * 1,
top: subBlockWidth * 6
);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragUpdate: (event) {
action = (event.delta.dx < 1)
? BlockMovement.LEFT
: BlockMovement.RIGHT;
},
onTap: () {
action = BlockMovement.ROTATE_CLOCKWISE;
},
child: AspectRatio(
aspectRatio: PLAY_FIELD_WIDTH / PLAY_FIELD_HEIGHT,
child: Container(
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(
width: GAME_AREA_BORDER_WIDTH,
color: Colors.black
),
borderRadius: BorderRadius.all(Radius.circular(2.5))
),
),
)
);
}
}
=== EDIT ====
Screenshot of full exception:

Convert Text to Speech with duration and progress bar

I have an issue in achieving text to speech conversion, am using flutter tts plugin for the text to audio converting, but i also need the duration of the audio for the progress bar and also for the timer but i could not able to get how i can achieve this,
this is what i need to achieve
i have tried this using flutterTts progress handler, but it only gives the offset values of start and end, am getting the text from API.
_flutterTts.setProgressHandler((String text, int startOffset, int endOffset, String word) {
setState(() {
_currentWord = word;
});
});
kindly help me to get the duration of the audio using flutterTts plugin,
any suggestion would be appreciated.
Given the example in the flutter_tts repo, you can simply add a progressHandler and LinearProgressIndicator.
Here is an example:
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
enum TtsState { playing, stopped, paused, continued }
class _MyAppState extends State<MyApp> {
late FlutterTts flutterTts;
dynamic languages;
String? language;
double volume = 0.5;
double pitch = 1.0;
double rate = 0.5;
bool isCurrentLanguageInstalled = false;
int end = 0;
String? _newVoiceText;
TtsState ttsState = TtsState.stopped;
get isPlaying => ttsState == TtsState.playing;
get isStopped => ttsState == TtsState.stopped;
get isPaused => ttsState == TtsState.paused;
get isContinued => ttsState == TtsState.continued;
bool get isIOS => !kIsWeb && Platform.isIOS;
bool get isAndroid => !kIsWeb && Platform.isAndroid;
bool get isWeb => kIsWeb;
#override
initState() {
super.initState();
initTts();
}
initTts() {
flutterTts = FlutterTts();
_getLanguages();
if (isAndroid) {
_getEngines();
}
flutterTts.setStartHandler(() {
setState(() {
print("Playing");
ttsState = TtsState.playing;
});
});
flutterTts.setCompletionHandler(() {
setState(() {
print("Complete");
ttsState = TtsState.stopped;
});
});
flutterTts.setCancelHandler(() {
setState(() {
print("Cancel");
ttsState = TtsState.stopped;
});
});
if (isWeb || isIOS) {
flutterTts.setPauseHandler(() {
setState(() {
print("Paused");
ttsState = TtsState.paused;
});
});
flutterTts.setContinueHandler(() {
setState(() {
print("Continued");
ttsState = TtsState.continued;
});
});
}
flutterTts.setErrorHandler((msg) {
setState(() {
print("error: $msg");
ttsState = TtsState.stopped;
});
});
flutterTts.setProgressHandler(
(String text, int startOffset, int endOffset, String word) {
setState(() {
end = endOffset;
});
});
}
Future _getLanguages() async {
languages = await flutterTts.getLanguages;
if (languages != null) setState(() => languages);
}
Future _getEngines() async {
var engines = await flutterTts.getEngines;
if (engines != null) {
for (dynamic engine in engines) {
print(engine);
}
}
}
Future _speak() async {
await flutterTts.setVolume(volume);
await flutterTts.setSpeechRate(rate);
await flutterTts.setPitch(pitch);
if (_newVoiceText != null) {
await flutterTts.awaitSpeakCompletion(true);
await flutterTts.speak(_newVoiceText!);
}
}
Future _stop() async {
var result = await flutterTts.stop();
if (result == 1) setState(() => ttsState = TtsState.stopped);
}
Future _pause() async {
var result = await flutterTts.pause();
if (result == 1) setState(() => ttsState = TtsState.paused);
}
#override
void dispose() {
super.dispose();
flutterTts.stop();
}
List<DropdownMenuItem<String>> getLanguageDropDownMenuItems() {
var items = <DropdownMenuItem<String>>[];
for (dynamic type in languages) {
items.add(DropdownMenuItem(value: type as String, child: Text(type)));
}
return items;
}
void changedLanguageDropDownItem(String? selectedType) {
setState(() {
language = selectedType;
flutterTts.setLanguage(language!);
if (isAndroid) {
flutterTts
.isLanguageInstalled(language!)
.then((value) => isCurrentLanguageInstalled = (value as bool));
}
});
}
void _onChange(String text) {
setState(() {
_newVoiceText = text;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter TTS'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: [
_inputSection(),
ttsState == TtsState.playing ? _progressBar(end) : Text(""),
_btnSection(),
languages != null ? _languageDropDownSection() : Text(""),
_buildSliders()
]))));
}
Widget _inputSection() => Container(
alignment: Alignment.topCenter,
padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
child: TextField(
onChanged: (String value) {
_onChange(value);
},
));
Widget _progressBar(int end) => Container(
alignment: Alignment.topCenter,
padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
child: LinearProgressIndicator(
backgroundColor: Colors.red,
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
value: end / _newVoiceText!.length,
));
Widget _btnSection() {
if (isAndroid) {
return Container(
padding: EdgeInsets.only(top: 50.0),
child:
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
_buildButtonColumn(Colors.green, Colors.greenAccent,
Icons.play_arrow, 'PLAY', _speak),
_buildButtonColumn(
Colors.red, Colors.redAccent, Icons.stop, 'STOP', _stop),
]));
} else {
return Container(
padding: EdgeInsets.only(top: 50.0),
child:
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
_buildButtonColumn(Colors.green, Colors.greenAccent,
Icons.play_arrow, 'PLAY', _speak),
_buildButtonColumn(
Colors.red, Colors.redAccent, Icons.stop, 'STOP', _stop),
_buildButtonColumn(
Colors.blue, Colors.blueAccent, Icons.pause, 'PAUSE', _pause),
]));
}
}
Widget _languageDropDownSection() => Container(
padding: EdgeInsets.only(top: 50.0),
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
DropdownButton(
value: language,
items: getLanguageDropDownMenuItems(),
onChanged: changedLanguageDropDownItem,
),
Visibility(
visible: isAndroid,
child: Text("Is installed: $isCurrentLanguageInstalled"),
),
]));
Column _buildButtonColumn(Color color, Color splashColor, IconData icon,
String label, Function func) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(icon),
color: color,
splashColor: splashColor,
onPressed: () => func()),
Container(
margin: const EdgeInsets.only(top: 8.0),
child: Text(label,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: color)))
]);
}
Widget _buildSliders() {
return Column(
children: [_volume(), _pitch(), _rate()],
);
}
Widget _volume() {
return Slider(
value: volume,
onChanged: (newVolume) {
setState(() => volume = newVolume);
},
min: 0.0,
max: 1.0,
divisions: 10,
label: "Volume: $volume");
}
Widget _pitch() {
return Slider(
value: pitch,
onChanged: (newPitch) {
setState(() => pitch = newPitch);
},
min: 0.5,
max: 2.0,
divisions: 15,
label: "Pitch: $pitch",
activeColor: Colors.red,
);
}
Widget _rate() {
return Slider(
value: rate,
onChanged: (newRate) {
setState(() => rate = newRate);
},
min: 0.0,
max: 1.0,
divisions: 10,
label: "Rate: $rate",
activeColor: Colors.green,
);
}
}

Flutter error: Instance member 'takePicture' can't be accessed using static access

I am a new developer and have just started using flutter. I was trying to make a camera app and I followed this tutorial:
https://www.youtube.com/watch?v=_nS00ZKnINQ
I am getting an error: Instance member 'takePicture' can't be accessed using static access.
You can view the entire file in this code block. Please don't hesitate to ask if you need the whole project directory.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class CameraScreen extends StatefulWidget {
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
CameraController cameraController;
List cameras;
int selectedCameraIndex;
String imgPath;
Future _initCameraController(CameraDescription cameraDescription) async {
if (cameraController != null) {
await cameraController.dispose();
}
cameraController =
CameraController(cameraDescription, ResolutionPreset.high);
cameraController.addListener(() {
if (mounted) {
setState(() {});
}
});
if (cameraController.value.hasError) {
print('!!!Camera error!!! ${cameraController.value.errorDescription}');
}
try {
await cameraController.initialize();
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
/// Display Camera Preview
Widget _cameraPreviewWidget() {
if (cameraController == null || cameraController.value.isInitialized) {
return const Text(
'Loading...',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
return AspectRatio(
aspectRatio: cameraController.value.aspectRatio,
child: CameraPreview(cameraController),
);
}
/// Display control bar with buttons to take picture
Widget _cameraControlWidget(context) {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FloatingActionButton(
child: Icon(
Icons.camera,
color: Colors.black,
),
backgroundColor: Colors.white,
onPressed: () {
_onCapturePressed(context);
})
],
),
));
}
/// Display a row of toggles to select the camera
Widget _cameraToggleRowWidget() {
if (cameras == null || cameras.isEmpty) {
return Spacer();
}
CameraDescription selectedCamera = cameras[selectedCameraIndex];
CameraLensDirection lensDirection = selectedCamera.lensDirection;
return Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: FlatButton.icon(
onPressed: _onSwitchCamera,
icon: Icon(
_getCameraLensIcon(lensDirection),
color: Colors.white,
size: 24,
),
label: Text(
'${lensDirection.toString().substring(lensDirection.toString().indexOf('.') + 1).toUpperCase()}',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
)),
));
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return null;
}
void _onSwitchCamera() {
selectedCameraIndex =
selectedCameraIndex < cameras.length - 1 ? selectedCameraIndex + 1 : 0;
CameraDescription selectedCamera = cameras[selectedCameraIndex];
_initCameraController(selectedCamera);
}
IconData _getCameraLensIcon(CameraLensDirection lensDirection) {
switch (lensDirection) {
case CameraLensDirection.back:
return CupertinoIcons.switch_camera;
case CameraLensDirection.front:
return CupertinoIcons.switch_camera_solid;
case CameraLensDirection.external:
return CupertinoIcons.photo_camera;
default:
return Icons.device_unknown;
}
}
}
void _onCapturePressed(context) async {
try {
final path =
join((await getTemporaryDirectory()).path, '${DateTime.now()}.png)');
await CameraController.takePicture(path);
} catch (e) {
_showCameraException(e);
}
}
void _showCameraException(CameraException e) {
String errorText = 'Error: ${e.code} \n Error Message: ${e.description}';
print(errorText);
}
I am using VS Code with Flutter on Windows 10
Do not call takePicture as a static method. You have to use your initialised controller:
cameraController.takePicture(path);
To access it, move your method _onCapturePressed into your class body. The working code from your tutorial can be found here.