Flutter Reactive Drawer Image

Itumeleng Masuluke
The Startup
Published in
5 min readApr 30, 2020
Photo by chuttersnap on Unsplash

A few days ago I ran into a bit of a flutter setState() challenge trying to update a user’s profile picture When updating the picture I wanted to update the avatar in the drawer as well. Usually it wouldn’t be a problem but because I had the drawer in a separate class, for modularization purposes, the drawers setState() wasn’t triggered when the image was updated. So I finally figured it out using rxdart and built a demo app to share the code. If you would like to skip ahead to the rxdart integration click here.

desired outcome

Initial Project Setup

From here on out this article will assume basic knowledge of flutter including package and asset addition.

Building our screen holder

We’ll start by creating our “Screenholder” class in a file called “screen_holder.dart”. This class contains our app’s scaffold and will be re-used with every screen in our app. The class has 3 required parameters, the scaffoldKey to uniquely identify the screen, the body to provide the screen’s widgets and the title for the app bar.

class ScreenHolder extends StatefulWidget {
final Widget body;
final String title;
final Color backgroundColor;
final GlobalKey<ScaffoldState> scaffoldKey;

const ScreenHolder(
{Key key,
@required this.body,
@required this.title,
@required this.scaffoldKey,
this.backgroundColor: Colors.white,
})
: super(key: key);

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

class ScreenHolderState extends State<ScreenHolder> {

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
) ,
drawer: Drawer(
child: Container()
),
body: widget.body
);
}
}

In our drawer we want to add our circle image view courtesy of this article by Paul Boldijar. Instead of a NetworkImage we will use a base64 image which we will store in a variable called “imageString”. if “imageString” doesn’t contain an image we will show a placeholder image instead. For the placeholder image add an image to your assets folder and call it “placeholder.png”. Now let’s declare our “imageString” variable and replace our empty Container() in the drawer with our this.

Padding(
padding: EdgeInsets.all(40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 150.0,
height: 150.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
image: new DecorationImage(
fit: BoxFit.fill,
image: imageString != null && imageString.isNotEmpty
? Image.memory(base64Decode(imageString)).image
: ExactAssetImage('assets/placeholder.png',
scale: 1.0)))),
]),
),

The file should look like this.

Profile Screen

The next thing we want to do is make our user profile screen, which in this example is our “main.dart” file. First we add title as a parameter in our “MyHomePage” class’ constructor. Next we remove the current counter code in our “_MyHomePageState” class and return an instance of “ScreenHolder” instead. We then declare our scaffoldKey and an “imageString” variable like we did in our “Screenholder” class. Our screen’s body will consist of the same circle image view as we have in our drawer and a text to prompt the user to click the image.

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

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

class _MyHomePageState extends State<MyHomePage> {
String imageString;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

@override
Widget build(BuildContext context) {
return ScreenHolder(
scaffoldKey: _scaffoldKey,
title: widget.title,
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: Center(
child: Column(
children: <Widget>[
Container(
width: 190.0,
height: 190.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
image: new DecorationImage(
fit: BoxFit.fill,
image: imageString != null &&
imageString.isNotEmpty
? Image.memory(base64Decode(imageString))
.image
: ExactAssetImage(
'assets/placeholder.png',
scale: 1.0)),
)),
SizedBox(
height: 10,
),
Text('Tap image to change')
],
),
)),
],
),
);
}
}

We will wrap our image view in a GestureDetector and use the onTap to select our image from our filesystem using the image_picker plugin.

GestureDetector(
child: Container(
//image Code
)),
onTap: () => GetImage()),

We will create an asynchronous function called GetImage() to handle the file picking. After we have chosen our file we will convert it to base64 and assign that to our “imageString” variable.

Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);

List<int> imageBytes = image.readAsBytesSync();
String base64Image = base64Encode(imageBytes);
imageChanged.add(base64Image);

setState(() {
imageString = base64Image;
});
}

Our main.dart file should now look like this

Now our app should be able to update the image in our “main.dart” file.

current progress

Adding Image Reactivity

Now that we have the project setup we can get to making the image in the drawer react to the profile photo change.

The hero of this feature comes from a flutter library called rxdart. More specifically the rxdart’s BehaviourSubject. The BehaviourSubject returns the latest value that was added to a stream. A more in-depth explanation of rxdart subjects can be found in this article by.

Setting up our global variables

After we have installed the rxdart package we want to create a new file for our global variables. In our “globals.dart” file we will declare our BehaviourSubject as a string type to correlate with our image’s base64 string.

We then want to subscribe to this BehaviourSubject in our “Screenholder” class.This should be done when the class is first initialized in the initState() function. So add this block of code in the “ScreenholderState” class.

@override
void initState() {
super.initState();
imageChanged.stream.listen((onData){
setState(() {
imageString = onData;
});
});
}

Now we won’t have to wait for the screen to be rebuilt to refresh the drawer, it will refresh whenever a new value is pushed into our BehaviourStream.

For the last bit, we want to add the base64 image to our stream right after we pick an image from our file system. So in the “getImage()” function, in our “main.dart” file, after the declaration of base64Image add this line of code in the “ScreenholderState” class.

imageChanged.add(base64Image);

And with that you should have a reactive drawer image. The complete code is available on my github. Happy coding.

--

--