Profile picture widget

This is a widget for showing a profile avatar, along with an edit button which lunches the camera. If there’s no photo to be loaded a placeholder consisting of an x amount of characters from the user’s name is going to be shown.

Dependencies

Add the following packages in the pubspec.yaml first.

  1. image_picker: ^version

and run the pub get command from the terminal.

Usage

Example usage with lists:

Center(
      child: ProfilePicture(
          photoUrl: widget.profile.avatar,
          username: widget.profile.name,
       )),

Parameters

@photoUrl : Optional. The link to an existing profile picture @assetImage : Optional. Placeholder (currently not in use) @username : Mandatory. Used for creating a placeholder from the first x letters. @setFileCallback : Optional. The function to be called once the new avatar is selected to pass it back to the parent (in order to upload it to the backend).

Widget

class ProfilePicture extends StatefulWidget {
  ProfilePicture(
      {this.photoUrl,
      this.assetImage = './lib/assets/logo.png',
      this.username,
      this.setFileCallback});

  final String photoUrl;
  final String assetImage;
  final String username;
  final Function(String) setFileCallback;

  @override
  _ProfilePictureState createState() => _ProfilePictureState();
}

class _ProfilePictureState extends State<ProfilePicture> {
  PickedFile _imageFile;
  dynamic _pickImageError;
  String _retrieveDataError;

  // the number of characters used for the placeholder image
  int numCharPlaceholder = 2;

  @override
  Widget build(BuildContext context) {
    final ImagePicker _picker = ImagePicker();

    void _onImageButtonPressed(ImageSource source,
        {BuildContext context}) async {
      try {
        final pickedFile = await _picker.getImage(
          source: source,
        );
        setState(() {
          _imageFile = pickedFile;
        });
      } catch (e) {
        setState(() {
          _pickImageError = e;
        });
      }
    }

    Future<void> retrieveLostData() async {
      final LostData response = await _picker.getLostData();
      if (response.isEmpty) {
        return;
      }
      if (response.file != null) {
        setState(() {
          _imageFile = response.file;
        });
      } else {
        _retrieveDataError = response.exception.code;
      }
    }

    Text _getRetrieveErrorWidget() {
      if (_retrieveDataError != null) {
        final Text result = Text(_retrieveDataError);
        _retrieveDataError = null;
        return result;
      }
      return null;
    }

    _buildStringPlaceholder() {
      return widget.username != null
          ? CircleAvatar(
              backgroundColor: Colors.lightBlue.shade50,
              child: Text(widget.username
                  .substring(0, numCharPlaceholder)
                  .toUpperCase()),
              radius: Dimens.avatarBig,
            )
          : CircleAvatar(
              backgroundColor: Colors.lightBlue.shade50,
              child: Text(Strings.appName
                  .substring(0, numCharPlaceholder)
                  .toUpperCase()),
              radius: Dimens.avatarBig,
            );
    }

    Widget _previewImage() {
      final Text retrieveError = _getRetrieveErrorWidget();
      if (retrieveError != null) {
        return retrieveError;
      }
      if (_imageFile != null) {
        if (kIsWeb) {
          // Why network?
          // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform
          return CircleAvatar(
            backgroundImage: NetworkImage(_imageFile.path),
            radius: Dimens.avatarBig,
          );
        } else {
          return Semantics(
              child: CircleAvatar(
                backgroundImage: FileImage(File(_imageFile.path)),
                radius: Dimens.avatarBig,
              ),
              label: 'image_picker_picked_image');
        }
      } else {
        if (_pickImageError != null) {
          ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Pick image error: $_pickImageError')));
        }
        return widget.photoUrl != null && widget.photoUrl.isNotEmpty
            ? CircleAvatar(
                backgroundImage: NetworkImage(widget.photoUrl),
                radius: Dimens.avatarBig,
              )
            : _buildStringPlaceholder();
      }
    }

    _buildAvatar() {
      return Container(
        child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android
            ? FutureBuilder<void>(
          future: retrieveLostData(),
          builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return widget.photoUrl != null &&
                    widget.photoUrl.isNotEmpty
                    ? CircleAvatar(
                  backgroundImage: NetworkImage(widget.photoUrl),
                  radius: Dimens.avatarBig,
                )
                    : _buildStringPlaceholder();
              case ConnectionState.done:
                if (_imageFile != null) {
                  widget.setFileCallback(_imageFile.path);
                }
                return _previewImage();
              default:
                if (snapshot.hasError) {
                  return Text(
                    'Pick image/video error: ${snapshot.error}}',
                    textAlign: TextAlign.center,
                  );
                } else {
                  return const Text(
                    'You have not yet picked an image.',
                    textAlign: TextAlign.center,
                  );
                }
            }
          },
        )
            : _previewImage(),
      );
    }

    return Container(
      child: Stack(
        children: <Widget>[
          _buildAvatar(),
          Positioned(
            top: 0,
            right: 0,
            child: IconButton(
              icon: new Icon(Icons.edit),
              onPressed: () {
                _onImageButtonPressed(ImageSource.camera,
                    context: context); //gallery: ImageSource.gallery
              },
            ),
          ),
        ],
      ),
    );
  }
}