Ahmadre / FlutterIconPicker

An adaptive comprehensive IconPicker for Flutter
MIT License
112 stars 76 forks source link

A problem with storing icon #19

Closed merskip closed 3 years ago

merskip commented 3 years ago

Hello,

I wanted to use icons as a symbol of a category. I need to store the selected icon in a database. The simplest way is store fields: codePoint, fontFamily, fontPackageand matchTextDirection. This solution is suggested in README file and in a few Stackoverflow posts but this doesn't work. After some time the icons are different.

In REDME there is an example: IconData(0xe3af, fontFamily: 'MaterialIcons'); // Icons.camera

but now under 0xe3af is: static const IconData record_voice_over_outlined = IconData(0xe3af, fontFamily: 'MaterialIcons');

This causes changing icons after some time. In the database, I store the above four fields. This means we cannot trust values for a long time. Using the code Icons.camera is ok and works for all time.

I think we need to store icons in a different way.

I think something like that:

  Map<String, String> iconToMap(IconPack iconPack, IconData icon) {
    if (iconPack == IconPack.material) {
      final iconEntry =
          icons.entries.firstWhere((iconEntry) => iconEntry.value == icon); // icons from src/material.icons.dart
      return {
        'pack': "material",
        'key': iconEntry.key,
      };
    }
    // etc.
  }
merskip commented 3 years ago

The full version of serializing and deserializing look like this:

import 'package:flutter/material.dart';
import 'package:flutter_iconpicker/IconPicker/Packs/Cupertino.dart'
    as Cupertino;
import 'package:flutter_iconpicker/IconPicker/Packs/FontAwesome.dart'
    as FontAwesome;
import 'package:flutter_iconpicker/IconPicker/Packs/LineIcons.dart'
    as LineAwesome;
import 'package:flutter_iconpicker/IconPicker/Packs/Material.dart' as Material;
import 'package:flutter_iconpicker/IconPicker/Packs/MaterialOutline.dart'
    as MaterialOutline;
import 'package:flutter_iconpicker/Models/IconPack.dart';

Map<String, dynamic> serializeIcon(IconData icon, {IconPack iconPack}) {
  if (iconPack == null) {
    if (icon.fontFamily == "MaterialIcons")
      iconPack = IconPack.material;
    else if (icon.fontFamily == "outline_material_icons")
      iconPack = IconPack.materialOutline;
    else if (icon.fontFamily == "CupertinoIcons")
      iconPack = IconPack.cupertino;
    else if (icon.fontPackage == "font_awesome_flutter")
      iconPack = IconPack.fontAwesomeIcons;
    else if (icon.fontPackage == "line_awesome_flutter")
      iconPack = IconPack.lineAwesomeIcons;
    else
      iconPack = IconPack.custom;
  }
  switch (iconPack) {
    case IconPack.material:
      return {
        'pack': "material",
        'key': _getIconKey(Material.icons, icon),
      };
    case IconPack.materialOutline:
      return {
        'pack': "materialOutline",
        'key': _getIconKey(MaterialOutline.materialOutline, icon),
      };
    case IconPack.cupertino:
      return {
        'pack': "cupertino",
        'key': _getIconKey(Cupertino.cupertinoIcons, icon),
      };
    case IconPack.fontAwesomeIcons:
      return {
        'pack': "fontAwesomeIcons",
        'key': _getIconKey(FontAwesome.fontAwesomeIcons, icon),
      };
      break;
    case IconPack.lineAwesomeIcons:
      return {
        'pack': "lineAwesomeIcons",
        'key': _getIconKey(LineAwesome.lineAwesomeIcons, icon),
      };
      break;
    case IconPack.custom:
      return {
        'pack': "custom",
        'iconData': {
          'codePoint': icon.codePoint,
          'fontFamily': icon.fontFamily,
          'fontPackage': icon.fontPackage,
          'matchTextDirection': icon.matchTextDirection,
        }
      };
    default:
      return null;
  }
}

IconData deserializeIcon(Map<String, dynamic> iconMap) {
  try {
    final pack = iconMap['pack'];
    final iconKey = iconMap['key'];
    switch (pack) {
      case "material":
        return Material.icons[iconKey];
      case "materialOutline":
        return MaterialOutline.materialOutline[iconKey];
      case "cupertino":
        return Cupertino.cupertinoIcons[iconKey];
      case "fontAwesomeIcons":
        return FontAwesome.fontAwesomeIcons[iconKey];
      case "lineAwesomeIcons":
        return LineAwesome.lineAwesomeIcons[iconKey];
      case "custom":
        final iconData = iconMap['iconData'];
        return IconData(
          iconData['codePoint'],
          fontFamily: iconData['fontFamily'],
          fontPackage: iconData['fontPackage'],
          matchTextDirection: iconData['matchTextDirection'],
        );
      default:
        return null;
    }
  } catch (e) {
    return null;
  }
}

String _getIconKey(Map<String, IconData> icons, IconData icon) =>
    icons.entries.firstWhere((iconEntry) => iconEntry.value == icon).key;

Nice would be optimize serializing for a large number of it.

Example of the result:

{
   "pack": "material",
   "key": "camera"
}
Ahmadre commented 3 years ago

The full version of serializing and deserializing look like this:


import 'package:flutter/material.dart';

import 'package:flutter_iconpicker/IconPicker/Packs/Cupertino.dart'

    as Cupertino;

import 'package:flutter_iconpicker/IconPicker/Packs/FontAwesome.dart'

    as FontAwesome;

import 'package:flutter_iconpicker/IconPicker/Packs/LineIcons.dart'

    as LineAwesome;

import 'package:flutter_iconpicker/IconPicker/Packs/Material.dart' as Material;

import 'package:flutter_iconpicker/IconPicker/Packs/MaterialOutline.dart'

    as MaterialOutline;

import 'package:flutter_iconpicker/Models/IconPack.dart';

Map<String, dynamic> serializeIcon(IconData icon, {IconPack iconPack}) {

  if (iconPack == null) {

    if (icon.fontFamily == "MaterialIcons")

      iconPack = IconPack.material;

    else if (icon.fontFamily == "outline_material_icons")

      iconPack = IconPack.materialOutline;

    else if (icon.fontFamily == "CupertinoIcons")

      iconPack = IconPack.cupertino;

    else if (icon.fontPackage == "font_awesome_flutter")

      iconPack = IconPack.fontAwesomeIcons;

    else if (icon.fontPackage == "line_awesome_flutter")

      iconPack = IconPack.lineAwesomeIcons;

    else

      iconPack = IconPack.custom;

  }

  switch (iconPack) {

    case IconPack.material:

      return {

        'pack': "material",

        'key': _getIconKey(Material.icons, icon),

      };

    case IconPack.materialOutline:

      return {

        'pack': "materialOutline",

        'key': _getIconKey(MaterialOutline.materialOutline, icon),

      };

    case IconPack.cupertino:

      return {

        'pack': "cupertino",

        'key': _getIconKey(Cupertino.cupertinoIcons, icon),

      };

    case IconPack.fontAwesomeIcons:

      return {

        'pack': "fontAwesomeIcons",

        'key': _getIconKey(FontAwesome.fontAwesomeIcons, icon),

      };

      break;

    case IconPack.lineAwesomeIcons:

      return {

        'pack': "lineAwesomeIcons",

        'key': _getIconKey(LineAwesome.lineAwesomeIcons, icon),

      };

      break;

    case IconPack.custom:

      return {

        'pack': "custom",

        'iconData': {

          'codePoint': icon.codePoint,

          'fontFamily': icon.fontFamily,

          'fontPackage': icon.fontPackage,

          'matchTextDirection': icon.matchTextDirection,

        }

      };

    default:

      return null;

  }

}

IconData deserializeIcon(Map<String, dynamic> iconMap) {

  try {

    final pack = iconMap['pack'];

    final iconKey = iconMap['key'];

    switch (pack) {

      case "material":

        return Material.icons[iconKey];

      case "materialOutline":

        return MaterialOutline.materialOutline[iconKey];

      case "cupertino":

        return Cupertino.cupertinoIcons[iconKey];

      case "fontAwesomeIcons":

        return FontAwesome.fontAwesomeIcons[iconKey];

      case "lineAwesomeIcons":

        return LineAwesome.lineAwesomeIcons[iconKey];

      case "custom":

        final iconData = iconMap['iconData'];

        return IconData(

          iconData['codePoint'],

          fontFamily: iconData['fontFamily'],

          fontPackage: iconData['fontPackage'],

          matchTextDirection: iconData['matchTextDirection'],

        );

      default:

        return null;

    }

  } catch (e) {

    return null;

  }

}

String _getIconKey(Map<String, IconData> icons, IconData icon) =>

    icons.entries.firstWhere((iconEntry) => iconEntry.value == icon).key;

Nice would be optimize serializing for a large number of it.

Example of the result:


{

   "pack": "material",

   "key": "camera"

}

hey @merskip sorry for my late response. I was very busy since last year and I have to investigate into this asap.

I see here a crucial bug, where I already got some ideas and yeah: your solution looks promising!

I'll write you here back, as soon as I finished the work on it :)

leonardarnold commented 3 years ago

As far as I know it's only about the material design code points, they could change at any time. This is quite annoying - not sure how other packages handle code points.

Ahmadre commented 3 years ago

@merskip @leonardarnold

just published a new version: Version

Thank you so much @merskip your solution is exactly what was needed :). Works like a charm.

I also added a complete example and you can test it yourself with the new example project.

merskip commented 3 years ago

Thank you for fixing it!

bambinoua commented 3 years ago

It is good to implement new functionality for icon serialization but it would be also good to notify users about breaking changes. I used method iconDataToMap/mapToIconData and after upgrage the package I got an error because you removed those ones and created news ones. Please update change log.

leonardarnold commented 3 years ago

thank you @Ahmadre i will check it out later! : - )

Ahmadre commented 3 years ago

It is good to implement new functionality for icon serialization but it would be also good to notify users about breaking changes. I used method iconDataToMap/mapToIconData and after upgrage the package I got an error because you removed those ones and created news ones. Please update change log.

Oh yeah, I just updated the README, but I will also update the Changelog and mark a big "Breaking Change" section 👍🏼

Ahmadre commented 3 years ago

It is good to implement new functionality for icon serialization but it would be also good to notify users about breaking changes. I used method iconDataToMap/mapToIconData and after upgrage the package I got an error because you removed those ones and created news ones. Please update change log.

Breaking Change is now mentioned in the changelog. Thank you for the reminder here :)

plekedev commented 3 years ago

There is another breaking change in my case, for previously stored icons there is no pack defined and the function deserializeIcon returns null Sample of stored icon: {codePoint: 62948, fontFamily: FontAwesomeSolid, fontPackage: font_awesome_flutter, matchTextDirection: false}

I fixed it by creating an IconData from the data if deserializeIcon returns null. I think this should be the default for deserializeIcon to handle previously saved icons.

IconData base64ToIconData(String iconJson) {
  IconData iconDataResult;
  if (iconJson == null || iconJson == '') {
    iconDataResult = Icons.block;
  } else {
    Map<String, dynamic> data = json.decode(utf8.decode(base64.decode(iconJson)));
    iconDataResult = deserializeIcon(data);
    if (iconDataResult == null) {
      iconDataResult = IconData(
        data['codePoint'],
        fontFamily: data['fontFamily'],
        fontPackage: data['fontPackage'],
        matchTextDirection: data['matchTextDirection'],
      );
    }
    if (iconDataResult == null) {
      iconDataResult = Icons.block;
    }
  }
  return iconDataResult;
}