How to use rive's state machine in flutter? - flutter

I've created a .riv file with 3 state animations: start, processing, end, which are in "State machine". Rive team recently announced a new feature with dinamically changing animations, it's "State machine". Not sure, how to use it in flutter project, i.e how to dynamically change value of animation. If somebody needs some code, no problem, I could provide. Moreover, link to rive's "state machine" https://www.youtube.com/watch?v=0ihqZANziCk. I didn't find any examples related to this new feature. Please help! Thanks.

The other answer is outdated.
class SimpleStateMachine extends StatefulWidget {
const SimpleStateMachine({Key? key}) : super(key: key);
#override
_SimpleStateMachineState createState() => _SimpleStateMachineState();
}
class _SimpleStateMachineState extends State<SimpleStateMachine> {
SMITrigger? _bump;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'bumpy');
artboard.addController(controller!);
_bump = controller.findInput<bool>('bump') as SMITrigger;
}
void _hitBump() => _bump?.fire();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: Center(
child: GestureDetector(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
fit: BoxFit.cover,
onInit: _onRiveInit,
),
onTap: _hitBump,
),
),
);
}
}
See the RIVE guide:
https://help.rive.app/runtimes/state-machines

There are examples on rives pub package site. Here is one for state machine.
example_state_machine.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive two boolean state machine inputs.
class ExampleStateMachine extends StatefulWidget {
const ExampleStateMachine({Key? key}) : super(key: key);
#override
_ExampleStateMachineState createState() => _ExampleStateMachineState();
}
class _ExampleStateMachineState extends State<ExampleStateMachine> {
/// Tracks if the animation is playing by whether controller is running.
bool get isPlaying => _controller?.isActive ?? false;
Artboard? _riveArtboard;
StateMachineController? _controller;
SMIInput<bool>? _hoverInput;
SMIInput<bool>? _pressInput;
#override
void initState() {
super.initState();
// Load the animation file from the bundle, note that you could also
// download this. The RiveFile just expects a list of bytes.
rootBundle.load('assets/rocket.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'Button');
if (controller != null) {
artboard.addController(controller);
_hoverInput = controller.findInput('Hover');
_pressInput = controller.findInput('Press');
}
setState(() => _riveArtboard = artboard);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: const Text('Button State Machine'),
),
body: Center(
child: _riveArtboard == null
? const SizedBox()
: MouseRegion(
onEnter: (_) => _hoverInput?.value = true,
onExit: (_) => _hoverInput?.value = false,
child: GestureDetector(
onTapDown: (_) => _pressInput?.value = true,
onTapCancel: () => _pressInput?.value = false,
onTapUp: (_) => _pressInput?.value = false,
child: SizedBox(
width: 250,
height: 250,
child: Rive(
artboard: _riveArtboard!,
),
),
),
),
),
);
}
}

Related

Flutter - SfCalendar - View not refreshing when changing the view

I am trying to create a view where the user can select the agenda view he wants (day, week, month...).
In my appBar, I have created an action icon where the user can select the agenda view he wants.
When I change the view, the set state does not refresh the agenda view. I do not find what I am missing.
If you could help, it will be appreciated. Thank you.
import 'package:syncfusion_flutter_calendar/calendar.dart';
import 'package:provider/provider.dart';
class CalendarWidget extends StatefulWidget {
CalendarView viewCalendar;
CalendarController _controller = CalendarController();
CalendarWidget(this.viewCalendar,this._controller, {Key? key}) : super(key: key);
#override
State<CalendarWidget> createState() => _CalendarWidgetState(viewCalendar,_controller);
}
class _CalendarWidgetState extends State<CalendarWidget> {
CalendarView viewCalendar;
CalendarController _controller;
#override
_CalendarWidgetState(this.viewCalendar,this._controller);
#override
Widget build(BuildContext context) {
return Column(
children: [
myCalendar(context,viewCalendar,_controller),
const SizedBox(height: 8.0),
],
);
}
}
Widget myCalendar (BuildContext context, view,_controler ) {
final events = Provider.of<EventProvider>(context).events;
final CalendarController _calendarControler = CalendarController();
_calendarControler.view = view;
return SfCalendar(
view: CalendarView.month,
// timeSlotViewSettings:
// const TimeSlotViewSettings(allDayPanelColor: Colors.green),
controller: _controler,
_controler.view = view,
//_controller.view = CalendarView.week,
showNavigationArrow: true,
showWeekNumber: true,
showDatePickerButton: true,
showCurrentTimeIndicator: true,
initialSelectedDate: DateTime.now(),
firstDayOfWeek: 1,
dataSource: EventDataSource(events),
onSelectionChanged: (details) {
final provider = Provider.of<EventProvider>(context, listen: false);
provider.setDate(details.date!);
},
onTap: (details) {
final provider = Provider.of<EventProvider>(context, listen: false);
if (provider.selectedDate == details.date) {
showModalBottomSheet(
context: context,
builder: (context) => const TasksWidget(),
);
}
},
onLongPress: (details) {
final provider = Provider.of<EventProvider>(context, listen: false);
provider.setDate(details.date!);
showModalBottomSheet(
context: context,
builder: (context) => const TasksWidget(),
);
},
);
}
class AgendaOrganize extends StatefulWidget {
const AgendaOrganize ({Key? key}) : super(key : key);
#override
_AgendaOrganizeState createState() => _AgendaOrganizeState();
}
class _AgendaOrganizeState extends State<AgendaOrganize> {
CalendarView viewCalendar = CalendarView.month;
final CalendarController _controller = CalendarController();
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: const MyMenu(),
appBar: AppBar(
title: const Center(
child: Text('Agenda')),
actions: <Widget>[
PopupMenuButton
(icon: const Icon(Icons.more_vert_outlined),
itemBuilder: (BuildContext context) {
return Menus.choice.map((String choice){
return PopupMenuItem(
value: choice,
child: Text(choice));
}).toList();
},
onSelected:
choiceMade,
),
IconButton(
icon: const Icon(
Icons.add_circle_outline,
color: Colors.white,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EventEditingPage()));
},
),
],),
body: CalendarWidget(viewCalendar),
//TODO //PROBLEME - SI J'AFFICHE PERSISTENTBOTTOMNAVBAR, affiche agenda FOR TODAY
// bottomNavigationBar: PersistentBottomNavBar(),
);
throw UnimplementedError();
}
#override
void setState(VoidCallback fn) {
viewCalendar = CalendarView.month;
super.setState(fn);
}
void choiceMade(String value) {
print(value);
setState(() {
viewCalendar = CalendarView.month;
});
}
}
class Menus {
static const List<String> choice = <String> [
'Day', 'Week', 'Work Week', 'Month','Schedule', 'Timeline Day', 'Timeline Week', 'Timeline Work Week'
];
}
Just add a key to the SfCalendar and it's going to change on every setState. Do it like the following:
Widget myCalendar(BuildContext context, CalendarView view) {
final events = Provider.of<EventProvider>(context).events;
final CalendarController _calendarControler = CalendarController();
return SfCalendar(
key: ValueKey(view), // <- Here
view: view,
...
Also, the CalendarWidget is passing the state further down to the _CalendarWidgetState itself. The _CalendarWidgetState should use widget.viewCalendar instead.
class CalendarWidget extends StatefulWidget {
CalendarView viewCalendar;
CalendarWidget(this.viewCalendar, {Key? key}) : super(key: key);
#override
State<CalendarWidget> createState() => _CalendarWidgetState();
}
class _CalendarWidgetState extends State<CalendarWidget> {
#override
Widget build(BuildContext context) {
return Column(
children: [
myCalendar(context, widget.viewCalendar),
const SizedBox(height: 8.0),
],
);
}
}
And here every choice possible:
void choiceMade(String value) {
setState(() {
switch (value) {
case 'Day':
viewCalendar = CalendarView.day;
break;
case 'Week':
viewCalendar = CalendarView.week;
break;
case 'Work Week':
viewCalendar = CalendarView.workWeek;
break;
case 'Month':
viewCalendar = CalendarView.month;
break;
case 'Schedule':
viewCalendar = CalendarView.schedule;
break;
case 'Timeline Day':
viewCalendar = CalendarView.timelineDay;
break;
case 'Timeline Week':
viewCalendar = CalendarView.timelineWeek;
break;
case 'Timeline Work Week':
viewCalendar = CalendarView.timelineWorkWeek;
break;
}
});
}
Based on the provided information, we have checked the mentioned issue “Calendar view not updating using Setstate”. View property of the SfCalendar is used to set the initial view of the calendar. For dynamic view changes, we have implemented the view property on the CalendarController. Kindly use that property from the controller for dynamic view changes. Please find the code snippet for dynamic view switching.
#override
void initState() {
_controller = CalendarController();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SafeArea(
child: Column(
children: [
Container(
height: 550,
child: SfCalendar(
view: CalendarView.day,
controller: _controller,
),
),
RaisedButton(
onPressed: () => _controller.view = CalendarView.week,
child: Text('Change the view'),
),
],
),
),
)));
} ```
Also please find the breaking changes from the following changelog link.
Changelog link: https://pub.dev/packages/syncfusion_flutter_calendar/changelog
Also, you can use the allowed views property of the calendar for view navigation. Please find the UG from the following link.
UG link: https://help.syncfusion.com/flutter/calendar/date-navigations#allowed-views
We hope that this helps you. Please let us know if you need further assistance.

How to save a boolean state in main.dart so it is retained throughout app

I have a challenge in retaining a boolean value state in my dashboard screen after I close or reload app.
On the dashboard screen, there is a ListTile where I can mark a card as verified by tapping on that card. Once the card is tapped, I set the bool verified state from false to true which works fine as long as I haven't closed or reloaded the app. Once the app is closed or reloaded, the boolean state is set back to false.
How can I initialize the boolean state in main.dart so that the verified status is always retained once it is set from the dashboard screen and can be used anywhere (more screens) within the app
here is the code:
Dashboard screen
class Dashboard extends StatefulWidget {
Dashboard({Key? key}) : super(key: key);
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
bool _verified = false;
//Retrieving card info from database
bool isFetching = false;
late String cardInfo = retrieveData; //url to php script for retrieving from database
List cardData = [];
getCardData() async {
setState(() => isFetching = true);
var response = await http.get(Uri.parse(cardInfo));
if (response.statusCode == 200) {
setState(() {
cardData = json.decode(response.body);
});
}
setState(() => isFetching = false);
return cardData;
}
#override
void initState() {
super.initState();
getCardData();
_verified;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Approve Card'),
centerTitle: true,
),
body: Container(
child: Card(
child: ListView.builder(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
primary: false,
itemCount: cardData.length, //coming from mysql database
itemBuilder: (context, index) {
return ListTile(
leading: Container(
padding: const EdgeInsets.only(left: 15.0),
alignment: Alignment.center,
height: 50,
width: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
image: DecorationImage(
image: NetworkImage(
'http://url/uploads/${cardData[index]['logo']}'),
fit: BoxFit.cover,
),
),
),
title: Text(
cardData[index]['name'],
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
cardData[index]['email'],
),
trailing: Bounce(
duration: const Duration(milliseconds: 100),
onPressed: () { //onPressed set verified state to true
//After app is reloaded, it is set back to false
setState(() {
col = iconTip;
_verified = true;
});
var url = Uri.parse(verifiedCards), //http url to php script
response = http.post(url, body: {
"card": cardData[index]['card'],
});
getCardData();
},
child: Container(
padding: const EdgeInsets.all(15.0),
color: col,
child: Icon(Icons.check_sharp),
),
),
);
}),
),
),
);
}
}
}
Main.dart screen
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: My Flutter App,
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.green,
backgroundColor: white,
),
),
initialRoute: '/',
routes: {
'/': (context) => const SplashScreen(),
'/dashboard': (context) => const Dashboard(),
},
);
}
}
Use the shared_preferences package to save data that needs to persist between app reloads.
https://pub.dev/packages/shared_preferences
// Save value
await prefs.setBool('verified', true);
// Read value
final bool? verified= prefs.getBool('verified');
When you want to change your application state entire application then you can use Provider Package.
First create Model Class
class MyChangeNotifier extends ChangeNotifier{
bool _verified;
void setVarified(bool verified){
_verified = verified;
notifyListeners();
}
bool get verified => _verified;
}
Attach Notifier with main.dart file.
runApp(MultiProvider(
providers: [
Provider<MyChangeNotifier>(create: (_) => MyChangeNotifier()),
],
child: MyApp(),
));
Use State in your application
// For change State from widgets
ElevatedButton(onPressed: (){
context.read<MyChangeNotifier>().setVarified([true / false]);
}, child: Text('Event Setter'));
// Access State for widget
bool check = context.read<MyChangeNotifier>().verified;
Also, you can check Flutter State Management in Hindi video tutorial for more help.
A complete example with Shared Preference :
Writing boolean value:
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('isfirstRun', false);
Reading boolean value:
SharedPreferences prefs = await SharedPreferences.getInstance();
bool myboolean = prefs.getBool(key) ?? true; // Or false depending of what you want for default value. This is required for Null safety
And don't forget to import share preference package:
import 'package:shared_preferences/shared_preferences.dart';
Thanks guys for your answers, I appreciate. I figured it out with the shared preferences I onced used. I was thinking there was a way to do so without using shared preferences but since it does the work and it's not too complicated, I decided to use it

How to toggle rive animation when tapped on the animation in flutter

I am a beginner to rive and flutter. I am building a favorite items page in flutter. If there are not anything added to favorites I need to show a riveAnimation on screen. I already implemented almost everything to show the animation on screen. But I need to toggle a jumping animation when user tap on the animation which is really cool. for now I have the animation on 'Idle' mode
You may want to refer to the rive file => Go to Rive. And I renamed Rive stateMachine name to Bird. Everything else is the same.
summary => I want bird to jump when user tap on him :)
The code and the image may be little bit bigger. Sorry about that
class Favourites extends StatefulWidget {
Favourites({Key? key}) : super(key: key);
#override
State<Favourites> createState() => _FavouritesState();
}
class _FavouritesState extends State<Favourites> {
String animation = 'idle';
SMIInput<String>? _birdInput;
Artboard? _birdArtboard;
void jump() {
setState(() {
_birdInput?.value = 'Pressed';
});
}
#override
void initState() {
super.initState();
rootBundle.load('assets/rive/bird.riv').then(
(data) {
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
var controller = StateMachineController.fromArtboard(
artboard,
'Bird',
);
if (controller != null) {
artboard.addController(controller);
_birdInput = controller.findInput('Pressed');
}
setState(() => _birdArtboard = artboard);
},
);
}
#override
Widget build(BuildContext context) {
final favourite = Provider.of<Favourite>(context);
return Scaffold(
backgroundColor: Colors.grey[300],
appBar: const CustomAppBar(title: 'Favourites'),
body: favourite.items.isEmpty
? Center(
child: Column(
children: [
SizedBox(
width: 300,
height: 500,
child: _birdArtboard == null
? const SizedBox()
: Center(
child: GestureDetector(
onTap: () {},
child: Rive(artboard: _birdArtboard!),
),
),
),
NeumorphicButton(),
],
),
)
: CustomGrid(),
);
}
}
If you open/run rive file on rive site, you can find that it is using Trigger variable for jumping and it is using State Machine 1 state machine.
Next thing comes about declaring variable. You need to use SMITrigger data type for this and use StateMachineController to control the animation.
Use .findSMI(..) instead of .findInput() for SMITrigger.
To start animation on trigger, use
trigger?.fire();
I will encourage you to take a look on editor and check input variable type while performing rive animation.
So the full widget that will provide animation is
class Favourites extends StatefulWidget {
const Favourites({Key? key}) : super(key: key);
#override
State<Favourites> createState() => _FavouritesState();
}
class _FavouritesState extends State<Favourites> {
String animation = 'idle';
Artboard? _birdArtboard;
SMITrigger? trigger;
StateMachineController? stateMachineController;
#override
void initState() {
super.initState();
rootBundle.load('assets/rive/bird.riv').then(
(data) {
final file = RiveFile.import(data);
final artboard = file.mainArtboard;
stateMachineController =
StateMachineController.fromArtboard(artboard, "State Machine 1");
if (stateMachineController != null) {
artboard.addController(stateMachineController!);
trigger = stateMachineController!.findSMI('Pressed');
stateMachineController!.inputs.forEach((e) {
debugPrint(e.runtimeType.toString());
debugPrint("name${e.name}End");
});
trigger = stateMachineController!.inputs.first as SMITrigger;
}
setState(() => _birdArtboard = artboard);
},
);
}
void jump() {
trigger?.fire();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[300],
body: Center(
child: Column(
children: [
SizedBox(
width: 300,
height: 400,
child: _birdArtboard == null
? const SizedBox()
: Center(
child: GestureDetector(
onTap: () {
jump();
},
child: Rive(artboard: _birdArtboard!),
),
),
),
],
),
));
}
}

Tap to focus for Flutter camera

A simple, but very complicated question: What’s the best way to add a tap to focus functionality for the Flutter camera?
I’ve searched the entire World Wide Web about elegant solutions, but I found nothing.
Do you have an idea?
I might be late but you can try adv_camera package.
Here is a simple example:
import 'package:adv_camera/adv_camera.dart';
import 'package:flutter/material.dart';
class CameraApp extends StatefulWidget {
final String id;
const CameraApp({Key? key, required this.id}) : super(key: key);
#override
_CameraAppState createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
List<String> pictureSizes = <String>[];
String? imagePath;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AdvCamera Example'),
),
body: SafeArea(
child: AdvCamera(
initialCameraType: CameraType.rear,
onCameraCreated: _onCameraCreated,
onImageCaptured: (String path) {
if (this.mounted)
setState(() {
imagePath = path;
});
},
cameraPreviewRatio: CameraPreviewRatio.r16_9,
focusRectColor: Colors.purple,
focusRectSize: 200,
),
),
floatingActionButton: FloatingActionButton(
heroTag: "capture",
child: Icon(Icons.camera),
onPressed: () {
cameraController!.captureImage();
},
),
);
}
AdvCameraController? cameraController;
_onCameraCreated(AdvCameraController controller) {
this.cameraController = controller;
this.cameraController!.getPictureSizes().then((pictureSizes) {
setState(() {
this.pictureSizes = pictureSizes ?? <String>[];
});
});
}
}

How to prevent multiple click on a gesture?

I have a GestureDetector that need to launch a url. But if the gesture gets multiple taps, then launch is called multiple times.
In the current code im trying to use a state _isButtonTapped to control the tap. But the .whenComplete is somehow call before the launch is preformed?
_isButtonTapped = false
Widget _buildButton(String key, Text title, String url) {
_onTapped() async {
if (await canLaunch(url)) {
launch(url).whenComplete(
() => setState(() {
_isButtonTapped = false;
}),
);
}
}
return GestureDetector(
onTap: () {
_isButtonTapped ? null : _onTapped();
setState(() {
_isButtonTapped = true;
});
},
child: Container(
child: Padding(
padding: EdgeInsets.all(6.0),
child: Center(child: title),
),
),
);
}
Try this:
class _HomePageState extends State<HomePage> {
bool _isButtonTapped = false;
String _url = "https://google.ca";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: GestureDetector(
onTap: () async {
if (!_isButtonTapped) { // only allow click if it is false
_isButtonTapped = true; // make it true when clicked
if (await canLaunch(_url)) {
await launch(_url);
_isButtonTapped = false; // once url is launched successfully, we again make it false, allowing tapping again
}
}
},
),
),
),
);
}
}
Try this? It should solve your problem.
class SafeOnTap extends StatefulWidget {
SafeOnTap({
Key? key,
required this.child,
required this.onSafeTap,
this.intervalMs = 500,
}) : super(key: key);
final Widget child;
final GestureTapCallback onSafeTap;
final int intervalMs;
#override
_SafeOnTapState createState() => _SafeOnTapState();
}
class _SafeOnTapState extends State<SafeOnTap> {
int lastTimeClicked = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final now = DateTime.now().millisecondsSinceEpoch;
if (now - lastTimeClicked < widget.intervalMs) {
return;
}
lastTimeClicked = now;
widget.onSafeTap();
},
child: widget.child,
);
}
}
You can wrap any kind of widget if you want.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children: [
// every click need to wait for 500ms
SafeOnTap(
onSafeTap: () => log('500ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('500ms click me')),
),
),
// every click need to wait for 2000ms
SafeOnTap(
intervalMs: 2000,
onSafeTap: () => log('2000ms'),
child: Container(
width: double.infinity,
height: 200,
child: Center(child: Text('2000ms click me')),
),
),
],
),
),
),
);
}
}
the easiest way is in inkWell widget put doubleTap: () {},
it will do nothing, when user click multiple time
You have a bug in your code.
You are setting _isButtonTapped to true everytime you press it.
Correct you onTap function:
return GestureDetector(
onTap: () {
if (_isButtonTapped == false){
_onTapped();
setState(() {
_isButtonTapped = true;
});
},
}
//...
Regarding why the whenComplete is not beign called when you expected, that's another problem. I never used it but tacking a quick look into the docs (https://api.flutter.dev/flutter/scheduler/TickerFuture/whenComplete.html) show us that are multiple ways of achiving this, including wraping the function in an try block and use thr finally cloused as the whenCompleted. You should take a look at he docs and tried it out. Can't help more with that detail.
Hope it helps you.