flutter icon indicating copy to clipboard operation
flutter copied to clipboard

Make Checkbox adaptive without delegating to `CupertinoCheckbox`

Open victorsanni opened this issue 1 year ago • 10 comments
trafficstars

Currently, Checkbox.adaptive delegates to CupertinoCheckbox when the current platform is iOS or macOS. This PR serves to make Material Checkbox configure to a Cupertino look and feel when the Checkbox.adaptive constructor is used on iOS or macOS.

Currently, delegating to CupertinoCheckbox means that many parameters to material Checkbox are ignored: https://github.com/flutter/flutter/blob/54e66469a933b60ddf175f858f82eaeb97e48c8d/packages/flutter/lib/src/material/checkbox.dart#L109-L115

This PR allows for an adaptive checkbox that keeps all the parameters to the regular material Checkbox, allowing for the same levels of customization, thus fixing issues like #134917.

https://github.com/flutter/flutter/issues/134917#issuecomment-1728605932 provides the value proposal behind this PR.

Before: 328349166-9c285f7d-98ec-4efa-8759-6f5fbc08f701-ezgif com-video-to-gif-converter

After: aftercheckboxadaptive-ezgif com-video-to-gif-converter

code sample
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

/// Flutter code sample for [Checkbox].

void main() => runApp(const CheckboxExampleApp());

class CheckboxExampleApp extends StatelessWidget {
  const CheckboxExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Checkbox Sample')),
        body: const Center(
          child: CheckboxExample(),
        ),
      ),
    );
  }
}

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

  @override
  State<CheckboxExample> createState() => _CheckboxExampleState();
}

class _CheckboxExampleState extends State<CheckboxExample> {
  bool? isChecked = true;

  @override
  Widget build(BuildContext context) {
    Color getColor(Set<WidgetState> states) {
      const Set<WidgetState> interactiveStates = <WidgetState>{
        WidgetState.pressed,
        WidgetState.hovered,
        WidgetState.focused,
      };
      if (states.any(interactiveStates.contains)) {
        return Colors.blue;
      }
      return Colors.red;
    }

    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        const Text('Cupertino Checkbox:'),
        CupertinoCheckbox(
          checkColor: Colors.white,
          tristate: true,
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
        const Text('Material Checkbox:'),
        Checkbox(
          checkColor: Colors.white,
          tristate: true,
          fillColor: WidgetStateProperty.resolveWith(getColor),
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
        const Text('Adaptive Checkbox'),
        Checkbox.adaptive(
          checkColor: Colors.white,
          tristate: true,
          fillColor: WidgetStateProperty.resolveWith(getColor),
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
      ],
    );
  }
}


Related PR: #130425

Pre-launch Checklist

victorsanni avatar May 06 '24 21:05 victorsanni

Somehow this PR doesn't trigger "Google testing", maybe mark this to be Ready to review? I think this PR change probably will break G3 and we might also need to add the Adaptation change, just like what Switch did(link).

QuncCccccc avatar May 07 '24 18:05 QuncCccccc

Can you share the code snippet that creates the video above? I am curious where the red color is coming from in the material one. Is this red color set directly on the Checkbox widget? If so, should the adaptive Checkbox on iOS honor that red color?

Piinks avatar May 08 '24 22:05 Piinks

Can you share the code snippet that creates the video above? I am curious where the red color is coming from in the material one. Is this red color set directly on the Checkbox widget? If so, should the adaptive Checkbox on iOS honor that red color?

The red color of the checkbox is because of the customization of the fillColor property to change colors depending on the WidgetState. This was done to show the customization ability with the new adaptive checkbox which did not previously exist in the former implementation that delegated to CupertinoCheckbox.

Code snippet

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

/// Flutter code sample for [Checkbox].

void main() => runApp(const CheckboxExampleApp());

class CheckboxExampleApp extends StatelessWidget {
  const CheckboxExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Checkbox Sample')),
        body: const Center(
          child: CheckboxExample(),
        ),
      ),
    );
  }
}

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

  @override
  State<CheckboxExample> createState() => _CheckboxExampleState();
}

class _CheckboxExampleState extends State<CheckboxExample> {
  bool? isChecked = true;

  @override
  Widget build(BuildContext context) {
    Color getColor(Set<WidgetState> states) {
      const Set<WidgetState> interactiveStates = <WidgetState>{
        WidgetState.pressed,
        WidgetState.hovered,
        WidgetState.focused,
      };
      if (states.any(interactiveStates.contains)) {
        return Colors.blue;
      }
      return Colors.red;
    }

    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        const Text('Cupertino Checkbox:'),
        CupertinoCheckbox(
          checkColor: Colors.white,
          tristate: true,
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
        const Text('Material Checkbox:'),
        Checkbox(
          fillColor: WidgetStateProperty.resolveWith(getColor),
          tristate: true,
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
        const Text('Adaptive Checkbox'),
        Checkbox.adaptive(
          fillColor: WidgetStateProperty.resolveWith(getColor),
          tristate: true,
          value: isChecked,
          onChanged: (bool? value) {
            setState(() {
              isChecked = value;
            });
          },
        ),
      ],
    );
  }
}


victorsanni avatar May 08 '24 22:05 victorsanni

One more thing. Maybe we should link to Checkbox.adaptive in the docs for CupertinoCheckbox under "See also".

justinmc avatar May 08 '24 22:05 justinmc

One more thing. Maybe we should link to Checkbox.adaptive in the docs for CupertinoCheckbox under "See also".

Yes, this is what is still unclear to me. Do we want folks to migrate and stop using CupertinoCheckbox? With 2 iOS implementations of checkbox now, what happens to the cupertino one?

Piinks avatar May 08 '24 22:05 Piinks

code snippet

Thanks for the code! Do you think the adaptive one should be red on iOS? Since it is given the same fillColor: WidgetStateProperty.resolveWith(getColor), or will the adaptive Checkbox still ignore some properties here?

Piinks avatar May 08 '24 22:05 Piinks

Some of these discussion threads can probably be moved into the doc, Github UI is tedious for conversations. 😅

Piinks avatar May 08 '24 22:05 Piinks

code snippet

Thanks for the code! Do you think the adaptive one should be red on iOS? Since it is given the same fillColor: WidgetStateProperty.resolveWith(getColor), or will the adaptive Checkbox still ignore some properties here?

I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the fillColor property, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.

With the previous adaptive checkbox, fillColor would have been ignored, and so the checkbox would always have the blue color no matter what.

victorsanni avatar May 08 '24 23:05 victorsanni

I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the fillColor property, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.

I agree! It looks like in the video it does not reflect that red color though?

Piinks avatar May 08 '24 23:05 Piinks

I think the adaptive Checkbox should be red on iOS, because now it no longer ignores the fillColor property, which allows for as much customization as is possible with material checkbox while still keeping the fundamental properties of a cupertino-looking checkbox.

I agree! It looks like in the video it does not reflect that red color though?

Oh I see what you mean.

Actually I am just realizing I was supposed to upload two videos (before and after) and just uploaded one (the before). So the current video is the behavior with the previous adaptive checkbox. I'll add the second video and annotate properly.

Thanks for pointing that out, I totally missed it.

victorsanni avatar May 08 '24 23:05 victorsanni