1

I'm building a Flutter app importing the Rive package. I've found a set of animated icons which I implemented in the app's sidebar. I tried creating my animated icon (folder) by copying exactly the package downloaded "style". The sidebar is animated: when the user clicks on one of the elements of the list tile, it is highlighted. However, an error occurred. So, the error comes from my rive animation, not a part of my code, I suppose. If you're familiar with Rive I'm imploring you to help me because I'm getting crazy. Thank you

This is the error shown after I added my animation:

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'SMIBool' in type cast
#0      _SideMenuState.build.<anonymous closure>.<anonymous closure>
side_menu.dart:56
#1      RiveAnimationState._init
rive_animation.dart:304
#2      RiveAnimationState._configure
rive_animation.dart:196
<asynchronous suspension>

Here is the code:

side_menu.dart

import 'package:flutter/material.dart';
import 'package:gym/models/rive_asset.dart';
import 'package:gym/utils/rive_utils.dart';
import 'package:gym/widgets/SideMenu/info_card.dart';
import 'package:gym/widgets/SideMenu/side_menu_tile.dart';
import 'package:rive/rive.dart';

class SideMenu extends StatefulWidget {
  const SideMenu({super.key});

  @override
  State<SideMenu> createState() => _SideMenuState();
}

class _SideMenuState extends State<SideMenu> {
  RiveAsset selectedMenu = sideMenu.first;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      width: 288,
      height: double.infinity,
      color: Theme.of(context).colorScheme.primary.withOpacity(.5),
      child: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const InfoCard(
              name: 'name',
              nomeUtente: 'user name',
            ),
            Padding(
              padding: const EdgeInsets.only(
                left: 24,
                top: 32,
                bottom: 16,
              ),
              child: Text(
                'Browse',
                style: Theme.of(context)
                    .textTheme
                    .titleMedium!
                    .copyWith(color: Colors.white70),
              ),
            ),
            ...sideMenu.map(
              (menu) => SideMenuTile(
                menu: menu,
                riveonInit: (artboard) {
                  StateMachineController controller =
                      RiveUtils.getRiveController(
                    artboard,
                    stateMachineName: menu.stateMachineName,
                  );
                  menu.input = controller.findSMI('active') as SMIBool;
                },
                press: () {
                  menu.input!.change(true);
                  Future.delayed(
                    const Duration(milliseconds: 500),
                    () {
                      menu.input!.change(false);
                    },
                  );
                  setState(() {
                    selectedMenu = menu;
                  });
                },
                isActive: selectedMenu == menu,
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(
                left: 24,
                top: 32,
                bottom: 16,
              ),
              child: Text(
                'History',
                style: Theme.of(context)
                    .textTheme
                    .titleMedium!
                    .copyWith(color: Colors.white70),
              ),
            ),
            ...sideMenu2.map((menu) => SideMenuTile(
                  menu: menu,
                  riveonInit: (artboard) {
                    StateMachineController controller =
                        RiveUtils.getRiveController(
                      artboard,
                      stateMachineName: menu.stateMachineName,
                    );
                    menu.input = controller.findSMI('active') as SMIBool;
                  },
                  press: () {
                    menu.input!.change(true);
                    Future.delayed(
                      const Duration(milliseconds: 500),
                      () {
                        menu.input!.change(false);
                      },
                    );
                    setState(() {
                      selectedMenu = menu;
                    });
                    menu.function(context);
                  },
                  isActive: selectedMenu == menu,
                )),
            const SizedBox(
              height: 30,
            ),
            const SizedBox(
              height: 30,
              width: 30,
              child: RiveAnimation.asset(
                'assets/RiveAssets/folder.riv',
                fit: BoxFit.cover,
              ),
            ),
          ],
        ),
      ),
    ));
  }
}

side_menu_tile.dart:

import 'package:flutter/material.dart';
import 'package:gym/models/rive_asset.dart';
import 'package:rive/rive.dart';

class SideMenuTile extends StatelessWidget {
  const SideMenuTile({
    super.key,
    required this.menu,
    required this.press,
    required this.riveonInit,
    required this.isActive,
  });

  final RiveAsset menu;
  final VoidCallback press;
  final ValueChanged<Artboard> riveonInit;
  final bool isActive;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Padding(
          padding: EdgeInsets.only(left: 24),
          child: Divider(
            color: Colors.white24,
            height: 1,
          ),
        ),
        Stack(
          children: [
            AnimatedPositioned(
              duration: const Duration(milliseconds: 300),
              curve: Curves.fastOutSlowIn,
              height: 56,
              width: isActive ? 288 : 0,
              left: 0,
              child: Container(
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.primary.withOpacity(.3),
                  borderRadius: const BorderRadius.all(Radius.circular(10)),
                ),
              ),
            ),
            ListTile(
              onTap: press,
              leading: SizedBox(
                height: 34,
                width: 34,
                child: RiveAnimation.asset(
                  menu.src,
                  artboard: menu.artboard,
                  onInit: riveonInit,
                ),
              ),
              title: Text(
                menu.title,
                style: const TextStyle(color: Colors.white),
              ),
            ),
          ],
        ),
      ],
    );
  }
}

model rive_asset.dart:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:gym/screens/splash_screen.dart';
import 'package:rive/rive.dart';

class RiveAsset {
  final String artboard, stateMachineName, title, src;
  final Function function;
  late SMIBool? input;

  RiveAsset(
    this.src, {
    required this.artboard,
    required this.stateMachineName,
    required this.title,
    required this.function,
    this.input,
  });

  set setInput(SMIBool status) {
    input = status;
  }
}

List<RiveAsset> bottomNavs = [
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'CHAT',
    stateMachineName: 'CHAT_Interactivity',
    title: 'Chat',
    function: () {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'SEARCH',
    stateMachineName: 'SEARCH_Interactivity',
    title: 'Search',
    function: () {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'TIMER',
    stateMachineName: 'TIMER_Interactivity',
    title: 'Timer',
    function: () {},
  ),
  RiveAsset('assets/RiveAssets/icons.riv',
      artboard: 'BELL',
      stateMachineName: 'BEll_Interactivity',
      title: 'Notifications',
      function: () {}),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'USER',
    stateMachineName: 'USER_Interactivity',
    title: 'Profile',
    function: () {},
  ),
];

List<dynamic> sideMenu = [
  // RiveAsset(
  //   'assets/RiveAssets/icons.riv',
  //   artboard: 'HOME',
  //   stateMachineName: 'HOME_Interactivity',
  //   title: 'Home',
  //   function: (context) {},
  // ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'USER',
    stateMachineName: 'USER_Interactivity',
    title: 'Profile',
    function: (context) {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'SEARCH',
    stateMachineName: 'SEARCH_Interactivity',
    title: 'Search',
    function: (context) {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'LIKE/STAR',
    stateMachineName: 'STAR_Interactivity',
    title: 'Favorites',
    function: (context) {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'FOLDER',
    stateMachineName: 'FOLDER_Interactivity',
    title: 'Datas',
    function: (context) {},
  ),
];

List<RiveAsset> sideMenu2 = [
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'CHAT',
    stateMachineName: 'CHAT_Interactivity',
    title: 'Help',
    function: (context) {},
  ),
  RiveAsset(
    'assets/RiveAssets/icons.riv',
    artboard: 'BELL',
    stateMachineName: 'BELL_Interactivity',
    title: 'Notifications',
    function: (context) async {
      await FirebaseAuth.instance.signOut();
      Navigator.of(context).pushReplacement(MaterialPageRoute(
        builder: (context) => const SplashScreen(),
      )); //this function is here just to test the app during the login
    },
  ),
];

rive_utils.dart:

import 'package:rive/rive.dart';

class RiveUtils {
  static StateMachineController getRiveController(Artboard artboard,
      {stateMachineName = 'State Machine 1'}) {
    StateMachineController? controller =
        StateMachineController.fromArtboard(artboard, stateMachineName);
    artboard.addController(controller!);
    return controller;
  }
}

I also provide the .riv file to get the access to the animated icons: Rive Animations

If you need something I hadn't provided don't hesitate to ask.

  • It appears like it's not "your" code, but it's actually rive code using the callback that you provided. It's almost certainly one of the `controller.findSMI('active') as SMIBool` casts, i.e. one of these `findSMI()` return null. – Tomasz Noinski Aug 20 '23 at 17:36
  • @TomaszNoinski I'm sorry but I don't fully understand it. How should I proceed? – Francesco Zanoncelli Aug 20 '23 at 19:40
  • Maybe change the controller.findSMI('active') as SMIBool to say "as SMIBool?" because one of them is returning null. But then you need to rework the surrounding code to handle what happens when it gets null. – John Weidner Aug 20 '23 at 19:51
  • I already tried that and I don't know how to handle the exception. I'm trying to figure out where the error is in the .riv file. The code works for every icon except for the 'datas' (or 'folder') one. Thanks for your time! – Francesco Zanoncelli Aug 20 '23 at 20:40

1 Answers1

0

It seems that the problem is the cast, change it. I changed the line and it works fine for me. By the way I like the icons :)

change in side_menu.dart line 56:

  menu.input = controller.findSMI('active') as SMIBool;

to:

  menu.input = controller.findSMI<SMIBool>('active');

enter image description here

video demo app animated icon

Franklin Diaz
  • 309
  • 1
  • 5
  • The code I provided works for every icon except for the 'datas' (or 'folder') one. Using your code an error occurs (_TypeError (Null check operator used on a null value)) in menu.input!.chang(true); The problem is somewhere in the .riv file or in the folder's RiveAsset. Thanks for your time! – Francesco Zanoncelli Aug 20 '23 at 20:38
  • oh, ok I'll check it out. meanwhile try change for menu.input?.chang(true); – Franklin Diaz Aug 20 '23 at 20:51
  • Does the folder icon have animation? – Franklin Diaz Aug 20 '23 at 20:54
  • the input of "folder" is null (it seems that it has no animation), so the only thing I can see that can be done is to change the operator !. to ?. menu.input?.change(true); and menu.input?.change(false); or better do an if(menu.input!=null){ code... } – Franklin Diaz Aug 20 '23 at 21:12
  • Yes, the icon has animation. I've created it just like the others have been. I'm going to check it again. – Francesco Zanoncelli Aug 20 '23 at 21:42
  • all the solutions that I have shared are so that the code does not give you an error but to tell the truth the root of the problem is in the icon "folder" State. if you have access when editing the .riv then the best thing you can do is try to fix it there that where the problem comes from. good luck! :) – Franklin Diaz Aug 20 '23 at 22:57
  • I know, thanks for your time! :) – Francesco Zanoncelli Aug 20 '23 at 23:19