Search code examples
flutterflutter-webriverpodhook-widgets

How to use HookWidget with useTextEditingController


I'm new to flutter development and trying to understand Riverpod.

I currently managed to make the following login form work with a StatefulWidget. Basically, the button becomes enabled if both fields are not empty and viceversa.

enter image description here

And this is the current code for it.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Login Demo',
      // theme: ThemeData(
      //   primarySwatch: Colors.blue,
      // ),
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 400,
            child: MyLoginPage2(),
          ),
        ),
      ),
    );
  }
}

class MyLoginPage extends StatefulWidget {
  const MyLoginPage({Key? key}) : super(key: key);

  @override
  State<MyLoginPage> createState() => _MyLoginState();
}

class _MyLoginState extends State<MyLoginPage> {
  final emailController = TextEditingController(text: '');
  final passwordController = TextEditingController(text: '');

  @override
  void initState() {
    super.initState();
    emailController.addListener(() {
      setState(() {});
    });
    passwordController.addListener(() {
      setState(() {});
    });
  }

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          controller: emailController,
          keyboardType: TextInputType.emailAddress,
        ),
        TextField(
          controller: passwordController,
          obscureText: true,
        ),
        areFieldsEmpty()
            ? const ElevatedButton(
                child: Text('Login disabled'),
                onPressed: null,
              )
            : ElevatedButton(
                child: const Text('Login enabled'),
                onPressed: () => print("this is where login happens"),
              )
      ],
    );
  }

  bool areFieldsEmpty() {
    return emailController.text.toString().isEmpty ||
        passwordController.text.toString().isEmpty;
  }
}

What I don't like is how the listeners call setState directly just to trigger a widget refresh. Is this how would you accomplish such a behavior?

I read great things about Riverpod, but I don't seem to grasp how to model the above behavior inheriting from HookWidget instead of StatefulWidget. Specifically, how to add the listeners to the text editing controllers.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Login Demo',
      // theme: ThemeData(
      //   primarySwatch: Colors.blue,
      // ),
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 400,
            child: MyLoginPage2(),
          ),
        ),
      ),
    );
  }
}

class MyLoginPage2 extends HookWidget {
  final emailController = useTextEditingController(text: '');
  final passwordController = useTextEditingController(text: '');

  MyLoginPage2({Key? key}) : super(key: key);

  // Pending questions
  // 1. Where do I add the listeners to the controllers?
  // 2. They are supposed to be auto dispose, right?

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          controller: emailController,
          keyboardType: TextInputType.emailAddress,
        ),
        TextField(
          controller: passwordController,
          obscureText: true,
        ),
        areFieldsEmpty()
            ? const ElevatedButton(
                child: Text('Login disabled'),
                onPressed: null,
              )
            : ElevatedButton(
                child: const Text('Login enabled'),
                onPressed: () => print("this is where login happens"),
              )
      ],
    );
  }

  bool areFieldsEmpty() {
    return emailController.text.toString().isEmpty ||
        passwordController.text.toString().isEmpty;
  }
}

Any help or tips will be appreciated.


Solution

  • To update the changes on HookWidget use useEffect(). We don't have to worry about dispose until create our custom hookWidget.

    
    class MyLoginPage2 extends HookWidget {
      const MyLoginPage2({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final emailController = useTextEditingController(text: '');
        final passwordController = useTextEditingController(text: '');
    
        final _areFieldsEmpty =
            useState<bool>(true); // controll the button based on Text.isEmpty
    
        bool areFieldsEmpty() {
          return emailController.text.toString().isEmpty ||
              passwordController.text.toString().isEmpty;
        }
    
        useEffect(() {
          emailController.addListener(() {
            _areFieldsEmpty.value = areFieldsEmpty();
          });
          passwordController.addListener(() {
            _areFieldsEmpty.value = areFieldsEmpty();
          });
        }, [
          emailController,
          passwordController,
        ]);
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
            ),
            TextField(
              controller: passwordController,
              obscureText: true,
            ),
            _areFieldsEmpty.value
                ? const ElevatedButton(
                    child: Text('Login disabled'),
                    onPressed: null,
                  )
                : ElevatedButton(
                    child: const Text('Login enabled'),
                    onPressed: () => print("this is where login happens"),
                  )
          ],
        );
      }
    }