Skip to main content

Adding Customizations: Allowing user to set default background image

 So right now in my Shopping List App, the user is able to change the background for each of their lists. Their choice is saved in Firebase Firestore so it will persist across devices and sessions. 

Right now the default background for every new list is the blue background. But what if the user doesn't want to have to change the background for every single list. What if they just want their favorite picture to be the background for every list. 

Well to make that possible I want to create a page that allows them to choose what the default background picture will be for every new list created. 

To do this I will create a document in the Firebase Firestore called defaultBackground and store the user's default selection there. This will be stored in a variable. When new lists are created it will fetch the image from that variable. This will only be used for the first time a list is created. Once a list is created to change the background they can do so inside of that list and it will overwrite the default background. 

First I will create a function that will create a document in the Firebase Firestore that will hold the first image. This document will not be tied to any list like the other background image documents are. 

createDefaultBackground() async {
await FirebaseFirestore.instance
.collection(userIdentification)
.doc('defaultBackground')
.get()
.then((val) async {
if (!val.exists) {
await FirebaseFirestore.instance
.collection(userIdentification)
.doc('defaultBackground')
.set({
'picture':
'https://firebasestorage.googleapis.com/v0/b/shopping-list-3e48a.appspot.com/o/steampunk.jpg?alt=media&token=ffbf6862-2046-4c88-8e72-650967fae421'
}).then((val) async {
await FirebaseFirestore.instance
.collection(userIdentification)
.doc('defaultBackground')
.get()
.then((val) async {
bpic.addAll(val.data());
});
});
}
bpic.addAll(val.data());
});
print('document created');
return bpic;
}

I tried to call this function in the init state but I think that since the userIdentification variable was not set yet an error would be called because that collection name did not exist yet. So I switched to calling the createBackgroundPicture function when the "Start a new list" button is pressed. Then it worked. The new list's background image was set from the defaultBackground document.

Next I want to add a button to the menu of the list page called "Customizations" this button will open a new page where the user can change the defaultBackground. 

TextButton(
child: Text("Customizations",
style: TextStyle(fontSize: 25, color: Colors.black)),
onPressed: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
CustomizationsPage()));
},
),


On the Customizations page, I will have it set up so that when the user clicks on Change default background the options will appear underneath. Then when they click on it again the options will disappear. I will achieve this by using the visibility widget. 

So I wrapped the text "Change Default Background" in a Textbutton and in the OnPressed parameter I toggeled the boolean variable "backgroundOps" between true and false. The backgroundOps variable is set to false. When the Change Default Background textbutton is tapped it changes the variable to true and the visibility widget is triggered to show the options beneath. 

Column(
children: [
TextButton(
onPressed: () {
setState(() {
backgroundOps = !backgroundOps;
});
},
child: Center(
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(backgroundOps
? Icons.arrow_drop_up
: Icons.arrow_drop_down),
Text("Change Default Background Image",
style: TextStyle(color: Colors.black, fontSize: 20)),
]),
),
),

Beneath the Change Default Background button is the Visibility widget which has a list view as its child. Since I want the list to be horizontal instead of vertical I set the scrollDirection to Axis.horizontal. I set the visible parameter equal to the backgroundOps variable, and the maintainSize parameter to false. The maintainSize parameter makes it so that when the list is not visible it will not take up any space. 

In the listview I am creating an outline button that will have an icon in its center. When this button is pressed the user is able to upload a picture from their gallery and use that as the default background of any new list that is created.  To make this happen in the On Pressed parameter I will put a couple functions to fetch the image from the user's gallery, upload the file to Firebase Cloud storage, then save the download URL to the Firebase Firestore. This will overwrite what was already saved as the default background. 

Visibility(
child: Container(
height: 300,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
OutlinedButton(
child: Container(
height: 100,
width: 100,
child: Center(
child: Icon(Icons.add_a_photo_sharp),
),
),
onPressed: () async {
await newPickedImage();
await changeDefaultBackgroundPic(newImage);
if (newImage != null) {
var snackmessage = SnackBar(
content: Text(
"Default Background Image has been changed"));
ScaffoldMessenger.of(context)
.showSnackBar(snackmessage);
} else {
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text("No Image Selected"),
content: Text("Please select an image."),
actions: [
OutlineButton(
child: Text("Try Again"),
onPressed: () {
Navigator.pop(context);
}),
],
));
}
},
),
],
),
),
visible: backgroundOps,
maintainSize: false),


I ran into an issue where after I selected a picture to upload from my gallery it would take a while for the snackbar to appear alerting me that the background Image had been changed. I thought that the holdup was in my ChangeDefaultBackgroundPic( ) function so I made a circle progress indicator appear once it was done with the newPickedImage( ) function. But when I tried it, it still took a while for the snackbar to appear and the progress indicator only flashed briefly before the snackbar. So that told me that the hold up was actually the newPickedImage( ) function and not the ChangeDefaultBackgroundPic( ) function. So I made the circle progress indicator trigger before the newPickedImage( ) function started and it would turn off after the ChangeDefaultBackgroundPic( ) function was finished. The user will not see the circle progress Indicator until after they chose their image to upload because they are briefly taken out of the app and into their gallery. 

onPressed: () async {
setState(() {
loading = true;
});
await newPickedImage();
await changeDefaultBackgroundPic(newImage);
setState(() {
loading = false;
});

Next, I want to add the other default background pictures that I offer in the app. They will be made into outlinedButtons as well. I want to make a widget that all I have to do is pass in their download Url and it will make each button function the same. 

class DefaultPicOptions extends StatefulWidget {
DefaultPicOptions({this.url});
final url;
@override
_DefaultPicOptionsState createState() => _DefaultPicOptionsState();
}

class _DefaultPicOptionsState extends State<DefaultPicOptions> {
_CustomizationsPageState customizationsPage = _CustomizationsPageState();
@override
Widget build(BuildContext context) {
return OutlinedButton(
child: Container(
height: 200,
width: 120,
child: Image(
image: CachedNetworkImageProvider(widget.url),
),
),
onPressed: () {
customizationsPage.changeDefaultBackgroundPic(widget.url);
if (widget.url != null) {
var snackmessage = SnackBar(
content: Container(
height: 100,
child: Text(
"Default Background Image has been changed",
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
));
ScaffoldMessenger.of(context).showSnackBar(snackmessage);
}
});
}
}

Comments