联系方式

  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-21:00
  • 微信:codinghelp

您当前位置:首页 >> Database作业Database作业

日期:2018-11-14 10:46

COMP7510

Internet Computing and Programming

Lab Manual 2 – Accessing Firebase Database and Storage

Part 4 – Firebase Service

Firebase is a service provided by Google. It provides us with functionalities including database, file

storage, analytics, messaging, and so on. In our labs, we only focus on the database and file storage. With

Firebase, our data can be stored online and accessed anywhere without maintaining the backend system

with data storages. No backend system is maintained by ourselves, so we need to pay more attention to

the data saving part of our program codes. Once the data are deleted or overwritten, they cannot be

recovered.

Enabling Firebase Service

To enable the Firebase service, we need a Google account. We can directly use our BU campus email

account because it is a Google account.

Let’s follow the steps below to enable Firebase service:

1. Go to the URL – https://console.firebase.google.com. If you have not yet logged in, log in with your

BU campus email account or your personal Google account.

2. Click “Add Project”.

2

3. On the pop-up panel, type “reach” for the project name, uncheck the option of Google Analytics

and check the option of the agreement. Then, click “Continue”.

4. Click “Create Project” shown in the bottom right corner of the second page of the pop-up panel.

5. When a message “Your new project is ready” shown, click “Continue”. You then should receive a

Welcome email from Firebase.

6. Go back to the Firebase console. In the left panel, expand the “Develop” list and select “Database”.

7. In the main panel, scroll down to find Real-time Database, and then click “Create database”.

8. On the pop-up panel, select “Start in test mode” and click “Enable”.

9. In the left panel, click “Storage”. Then, in the main panel, click “Get Started”.

10. On the pop-up panel, click “Got it”.

11. Now, the database and storage features are ready.

3

Data in Firebase Database

After creating the database, we can manage our data through the Firebase console. To view your

database, you can:

Click the “Database” in the left panel. Then, add “/data” to the end of the URL.

Manual Input

Next, we are going to add the data to the database manually. Let’s follow the steps below:

1. Move the mouse pointer over the root entry (reach-XXXX entry). Then, click its “+” sign to add a

child entry.

2. Type “users” for the name field, and then click its “+” sign to add a child entry.

3. Type your student ID for the name field, and then click its “+” sign to add a child entry.

4. Type “roles” for the name field, and then click its “+” sign to add a child entry.

5. Type “COMP 7510” for the name field, and type “student” for the value field.

6. Then, click “Add” to commit the creation.

Click the “X” sign if you want to delete an entry. All its child entries will be deleted too. And, no undo service

is provided.

4

Data Import

If you have a JSON data file on hand, you may import the data from the data file to your database. Let’s

follow the steps below to import the data:

1. Download the sample data file –

sampledata.json, from our course web page.

2. Click the rotated “…” button (more button) at

the top right corner of the main panel, and select

“Import JSON”.

3. Click “Browse” and select the sampledata.json

file. Then, click “Import” to commit.

4. Now, you have the data imported.

Data Export

If you want to make a backup copy of your existing data, you may use the export function by selecting

“Export JSON” in the menu of the more button.

Data Presentation

Now, the data are ready but we need to understand how they are presented in the Firebase database.

Some points you need to pay attention:

Firebase database is a NoSQL database. Data are not separated in different tables as a relational

database.

The data in Firebase database are shown as a tree structure.

A data entry is represented by key and value. The key is used to refer to the entry.

A key must be in string format. A value can be a string, number, or map structure.

Values may be duplicated. Denormalization is often in Firebase database.

There is no control for adding sub-entries to an entry.

Firebase database supports Unicode, so you can store Chinese words and symbols.

The long text will not be displayed completely in the console, but there is no problem with your

app.

5

Consider the following sample data segment:

The reach-68338 entry is the root of the database. It has two child entries – courses and users.

The courses entry has three child entries including ALL, COMP 7510 and COMP XXXX.

The notifications entry of the COMP 7510 entry contains two child entries

-LEEd6ApODe47AyMaMwF and -LEJKF9ainie8A4hCoD_. These notifications are about the

course COMP 7510. We express that the notifications will be read by COMP 7510 teachers and

students only.

-LEEd6ApODe47AyMaMwF is a key referring to a notification. The notification contains the

fields including title, content, course, created time, author, image links, a copy of the key. The keys

– “title”, “content”, “course”, “createdAt”, “createdBy”, “images”, and “key” refer to these fields

respectively.

The key entry is a copy of the key of the notification entry that will be used for making our

program shorter and run faster.

The notification -LEEd6ApODe47AyMaMwF has an images entry; The another one

-LEJKF9ainie8A4hCoD_ does not have an images entry. This is allowed in Firebase database,

but you need to handle this issue in your app.

The COMP XXXX entry does not have the notifications entry, which indicates no notification has

been posted for COMP XXXX yet.

6

Rules

When we create the database, we selected the option – “Start in test mode”. Now everyone can read and

write our databases through the database URL.

Now, we set the minimum security to the database by publishing the new rules as follows:

{

"rules": {

".read": "auth != null",

".write": "auth != null"

}

}

Files in Firebase Storage

Firebase Storage is a simple file online storage that allows us to upload, download and delete files. To

upload a file, we can simply click on the “Upload file” button at the top right corner of the “Storage” panel.

After uploading a file, you can check its detailed information by clicking on the file item. In its properties

panel, you can find a download URL for downloading the file. Or, you can check the file items to download

files in batch. You can also delete the selected files.

Upload

Check to download or delete

Download URL

Properties panel

Of course, we should not dispose our database URL.

Or, use an advanced authentication method. But,

the advanced authentication is out of our scope.

7

Google Sign-in & Firebase Authentication

As we enabled the authentication requirement of the database and the default security of the storage

requires user authentication, our app requires an authentication procedure for Firebase service.

Otherwise, our app cannot read and write the database and storage. Firebase accepts different

authentication methods including username/password login, Google sign-in, and Facebook sign-in. To

limit the usage of our app for HKBU’s students and staffs only with avoiding additional username and

password, we will use Google sign-in (remember that our campus email accounts are Google accounts).

Let’s follow the steps below to enable the Google authentication for Firebase:

1. Click “Authentication” in the left panel.

2. In the “Authentication” panel, click “Sign-in method”, and select “Google” sign-in provider.

3. Click the “Enable” button to enable the Google sign-in provider. And, try “REACH” for Public-facing

name.

4. Click “Save” to commit.

8

Part 5 – Adding Firebase to iOS App

The next procedure is to let our app access Firebase. Our app requires Firebase SDK so that it can access

the Firebase database and storage.

Installing Firebase SDK

Let’s follow the steps below:

1. Use Finder to locate the Runner.xcworkspace file in the Documents/reach/ios/ folder.

2. Double-click to open the Runner.xcworkspace file. Xcode then will be launched.

3. Use a web browser to open Firebase console, click “Project Overview”.

4. Click “Add Firebase to your iOS app”.

5. In the “Add Firebase to your iOS app” page, try

hk.edu.hkbu.comp.reach for the iOS bundle ID. Then, click

“Register App”.

6. Click “Download GoogleService-Info.plist” to download the

GoogleService-Info.plist file.

7. Drag and drop the GoogleService-Info.plist file to the

Project Navigator of Xcode under the Runner sub-folder.

8. In the pop-up window, enable the option “Destination: copy items if needed” and click “Finish”.

9. Close Xcode. And skip the remaining steps of the “Add Firebase to your iOS app” page.

10. Go back to IntellliJ, open the Info.plist file located in the /reach/ios/Runner folder. Add the

following lines before the </dict> tag.

<key>CFBundleURLTypes</key>

<array>

<dict>

<key>CFBundleTypeRole</key>

<string>Editor</string>

<key>CFBundleURLSchemes</key>

<array>

<string>xxxxxxxxxxxx</string>

</array>

</dict>

</array>

11. Open the pubspec.yaml file & add the following lines to the “dependencies” section.

google_sign_in: ^3.0.4 # for google sign-in

firebase_auth: ^0.5.15 # for Firebase sign-in

firebase_database: ^0.4.6 # for database access

firebase_storage: ^0.3.7 # for storage access

12. Save the file and click the “Package get” link.

xxxxxxxxxxxx should be replaced with

your own reversed client ID. You can find

it in your GoogleService-Info.plist file.

9

Firebase Initialization

The project setting is now ready. Next, we need to add program codes. We need to declare some variables

and initiate the Firebase module. Let’s add the following codes to the global.dart file.

1. Import the necessary packages.

import 'package:google_sign_in/google_sign_in.dart';

import 'package:firebase_auth/firebase_auth.dart';

import 'package:firebase_core/firebase_core.dart';

import 'package:firebase_database/firebase_database.dart';

import 'package:firebase_storage/firebase_storage.dart';

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:fluttertoast/fluttertoast.dart';

2. Declare some global variables and a function named firebaseInit() for initiating the Firebase

module. Add the following codes to the end of the global.dart file.

18

GoogleSignIn googleSignIn;

FirebaseAuth firebaseAuth;

DatabaseReference dbRef;

StorageReference storageRef;

String userID;

Map roles;

void firebaseInit() {

var firebaseApp = FirebaseApp.instance;

googleSignIn = GoogleSignIn(scopes: ['email']);

firebaseAuth = FirebaseAuth.instance;

dbRef = FirebaseDatabase(app: firebaseApp).reference();

storageRef =

FirebaseStorage(app:firebaseApp, storageBucket:'yyyyyyyyyy').ref();

Fluttertoast.showToast(msg: 'Initialization is done');

}

3. Open the main.dart file and change the main() function as follows:

void main() {

firebaseInit();

runApp(MyApp());

}

Sign-in

Next, we add the signIn() function for performing the sign-in procedure. The signIn() function will be

invoked when the user presses the icon button of the Home page. The sign-in logic is as follows:

i. Show the splash screen with the sign-in button. The On-Pressed event is triggered if the user

provides the sign-in button.

ii. Then, the GoogleSignIn.signIn() method will be invoked, and the Google sign-in page prompts.

iii. If the user signs in with HKBU campus email account, the account information (from Google signin

API) will be used for Firebase authentication. The user ID will be stored for further usage in

the app. The main menu screen shows as well.

yyyyyyyyyy should be replaced

with the URL of your Firebase

storage. You can find it in

Firebase console.

10

iv. If the user does not sign in with HKBU campus email account, an alert page shows about the

problem.

Let’s add the following procedures to add the sign-in logic:

1. Add the codes in the global.dart file:

Future<bool> signIn(context) async {

var account = await googleSignIn.signIn();

if (account != null && account.email.endsWith('hkbu.edu.hk')) {

var googleAuth = await account.authentication;

var user = await firebaseAuth.signInWithGoogle(

idToken: googleAuth.idToken,

accessToken: googleAuth.accessToken

);

userID = user.email.substring(0, user.email.indexOf('@'));

} else {

googleSignIn.signOut();

userID = null;

var alert = AlertDialog(

title: Text('Sign in'),

content: Text('Please sign in with your HKBU email account.'),

);

showDialog(context: context, builder: (_)=>alert);

}

return userID != null;

}

? Declare an asynchronized method, signIn() that performs Google sign-in by using the

googleSignIn.signIn() method (line 3). The await expression forces the program waiting

for the completion of the method.

Signed in with HKBU campus email account

Show

Alert dialog

Show menu screen with

sign-out button

Yes

Show

splash screen with sign-in button

NoGoogle

Sign-out

Google & Firebase

Sign-out

Google

Sign-in

Start

Sign-out button onPressed

handle

Sign-in

button onPressed

handle

Callback

function

declare

Callback

function

declare

11

Check whether the user signed in with HKBU email account (line 5). If yes, sign in Firebase

using the account information (line 7 ~ 12).

Otherwise, perform Google sign-out (line 16). And, show an alert dialog about the sign-in

requirement (line 19 ~ 24).

Finally, return the sign-in result (true = signed in; false = not signed in).

2. Open the home.dart file. In the splashScreen() method, change the callback function of the

Widget splashScreen() {

return Scaffold(

appBar: null,

body: Container(

width: MediaQuery.of(context).size.width,

child: Column(

IconButton(

icon: Icon(Icons.fingerprint),

iconSize: 64.0,

onPressed: () => signIn(context).then((success){

if (success) setState((){});

}),

}

Invoke the signIn() method. Then, we call the setState() method with empty body if the

result of the signIn() is true.

3. As we declare variable userID in the global.dart file. It can be accessed anywhere. So, we need

to delete the one declared inside the HomeState class. And, add the import statement at the top

of the home.dart file to import the global.dart file.

import 'global.dart';

class HomeState extends State {

// var userID = 'hello';

@override

Widget build(BuildContext context) { … }

4. Save the files and re-run the app.

5. In the splash screen, press the fingerprint icon to sign in. The Google sign-in screen will be shown.

6. Type your HKBU email address and password to sign in. Then, you should see the main menu

screen.

12

Sign-out

Comparing with the sign-in procedure, the sign-out procedure is much simpler. We only need to set

userID to null and call the signOut() methods of firebaseAuth and googleSignIn.

1. Add the following codes to the end of the global.dart file:

void signOut() async {

userID = null;

await firebaseAuth.signOut();

await googleSignIn.signOut();

}

2. Open the home.dart file, go to the menuScreen() method. And, change the callback function of

the icon button nested in the app bar as follows

Widget menuScreen() {

return Scaffold(

appBar: AppBar(

title: Text('REACH'),

actions: <Widget>[

IconButton(

icon: Icon(Icons.account_box),

onPressed: (){

signOut();

setState((){});

},

),

],

),

3. Save the files and re-run your app. Then, click the icon button on the main menu screen to sign

out.

Retrieving Data from Firebase Database

The DatabaseReference class is used for representing a particular position in the Firebase database. In

the initialization of the Firebase module, we set variable dbRef to the root of our database using the

following statement.

dbRef = FirebaseDatabase(app: firebaseApp).reference();

Its child() method is used to specify another position under the current position. Consider the following

sample data segments stored in the Firebase database:

dbRef represents the root of the database –

reach-68338.

To represent my roles, we do:

DatabaseReference rolesRef =

dbRef.child('users/mandel/roles');

After we set a data reference to a position, we can get the data by the data reference. We discuss two

Firebase database API methods – blocking method and listener method.

13

Blocking method – once()

The once() method is an asynchronized method that returns a future snapshot object. The method call is

a one-time operation. For example, we need to retrieve the roles of a user. So, we do:

Future<void> getRoles() async {

var rolesRef = dbRef.child('users/$userID/roles');

var snapshot = await rolesRef.once();

roles = snapshot.value as Map;

}

The data reference rolesRef points to the position ‘users/$userID/roles’(line 3). Assume that variable

userID is storing an ID of a user, and $userID in the string will be replaced with the value of variable

userID.

The once() method is an asynchronized method to download the data from the database

(line 4).

o The await expression is added to wait until the asynchronized data download operation

completes.

o We have to use an asynchronized method (a method declared with async expression)to store the

statements if we use the await expression.

o The once() method returns a DataSnapshot object.

We retrieve the value of the DataSnapshot object, convert it to a Map, and assign it to the global

variable roles (line 5).

o If userID equals ‘mandel’, variable roles will be:

{ ‘ALL’ : ‘administrator’, ‘COMP 7510’ : ‘teacher’, ‘COMP XXXX’ : ‘administrator’ }

If we do not use the await expression, we can use then() method with a callback function to retrieve the

data.

void getRoles() {

var rolesRef = dbRef.child('users/$userID/roles');

rolesRef.once().then((snapshot) => roles = snapshot.value as Map);

}

14

Listener method – onValue.listen()

The onValue property of the data reference points to a stream connecting to the database. We retrieve

the data by attaching a listener to the stream. The listener is triggered once for the initial state of the data

and again anytime the data changes.

The following is the example codes to retrieve roles using listener method:

void getRoles() {

var rolesRef = dbRef.child('users/$userID/roles');

rolesRef.onValue.listen((event){

roles = event.snapshot.value as Map;

});

}

Retrieving Notifications in Our App

Imagine that we have many courses and each course has its

notifications for its teachers and students. Now, a student takes

COMP7510 only, he/she should be able to read the notifications

of COMP7510 and the public notifications (for all people). Of

course, he/she should not be able to read the notifications of

other courses. The same idea is also applicable to the teachers

and administrators who work for different courses.

The child entries of the roles entry store the user roles of

different courses, and the course codes are used as keys of the

entries. These keys can be used to determine which course

notifications should be shown to the users.

Maps

A map is a structure that manages the values by using keys. Both keys and values can be any type

of objects. Each key occurs only once, but the map can store the same value multiple times.

A map can be created and initialized with a set of elements with keys contained in a set of braces

“{ }”, or using the constructor of the Map class – Map<K, V>().

Declaration:

var map1 = {

'Alice' : '852-66558899',

'Bob' : '852-98745612',

};

var map2 = Map<String, String>();

Adding / setting an element:

map1['Cathy'] = '852-56789012';

Deleting an element:

map1.remove('Bob');

Access an element:

var phone = map1['Alice'];

15

Adding Code

The flow of retrieving the roles and notifications is as follows:

1. Before constructing the screen, during the initial state, we send queries to the Firebase database

for retrieving the user roles and notifications.

2. The build() method is invoked then to construct the screen with an empty list view because the

data is not yet delivered.

3. The callback function will be invoked when the data is ready. The callback function calls the

build() method again to reconstruct the screen with the newly received data.

Let’s follow the steps below to add the codes to the app:

1. Add the following method to the global.dart file:

Future<void> getRoles() async {

var rolesRef = dbRef.child('users/$userID/roles');

var snapshot = await rolesRef.once();

roles = snapshot.value as Map;

}

2. Open the notification_list.dart file and put the following line to the top of the file.

import 'package:intl/intl.dart';

Callback

declare function

receive

data

build()

invoke

Firebase

database

query

Render

screen

Data

changed

handle

issue

initState()

Start

perform

16

3. put the following codes to the top of the NotificationListState class:

var canCreate = false;

var nMap = {};


void getNotificationList() {

Set roleSet, courseSet;

if (roles != null) {

roleSet = roles.values.toSet();

courseSet = roles.keys.toSet();

} else {

roleSet = Set();

courseSet = Set();

}

courseSet.add('ALL');

canCreate = roleSet.contains('teacher')

|| roleSet.contains('administrator');

for (var c in courseSet) {

var nRef = dbRef.child('courses/$c/notifications');

nRef.onValue.listen((event) {

if (event.snapshot.value == null) nMap.remove(c);

else nMap[c] = (event.snapshot.value as Map).values.toList();

if (mounted) setState(() {});

});

}

}

Variable canCreate represents whether the user can create new notifications or not (line 1).

Variable nMap is a map for storing the notifications downloaded from the Firebase database

(line 2).

The getNotificationList() method is used to retrieve the notifications from different courses.

The global variable “roles” stores the user roles – course codes are keys, and roles are values.

We separate the keys and the values and store them as Sets(line 5 ~ 12). If the global variable

“roles” equals to null, it indicates that there is no information about the user roles in the

Firebase database.

A new element ‘ALL’ is added to the course set. It indicates the user can read the public

notifications. (line 13).

The contains() method is used to check whether roleSet contains ‘teacher’ or ‘administrator’.

The Boolean result is stored in variable canCreate that will be used later

(line 14 ~ 15).

The for-in loop reads each element of the course set and creates a listener to the data

references for listening to the data changes about the notifications (starting from

line 17).

Listeners and callback functions are declared for listening and handling the data changes of

different courses. Once the data change occurs, the callback function (line 20 ~ 25) will be

invoked. In the callback function we do the following actions:

17

o If the snapshot contains nothing (no notification for the course), we delete the old

notifications about the course. Otherwise, we use the new set of notifications to replace

the existing set stored in the map.

o If the notification list page is active, we rebuild the screen (line 23).

4. Add the following initState() method to the NotificationListState class. It is used to invoke the

methods we declared above.

@override

void initState() {

super.initState();

getRoles().then((_) => getNotificationList());

}

Invoke the getRoles() method (line 4). Its callback function then will call the

getNotificationList() for downloading the notifications.

For-in loop

The for statement can be used to declare a for-in loop.

Syntax:

for(var v in iterable) {

segment

}

In the expression, an element will be picked from the iterable (e.g., List, Set) each time until the

loop walks through all elements in the iterable.

Example:

for(var c in [‘H’, ‘E’, ‘L’, ‘L’, ‘O’]) {

print(c);

}

The code above prints five lines, they are respectively ‘H’, ‘E’, ‘L’, ‘L’ and ‘O’.

18

5. Open the notification_list.dart file and modify the build() function of the NotificationListState

class:

@override

Widget build(BuildContext context) {

var widgetList = <Widget>[];

var data = List();

for (List c in nMap.values)

data.addAll(c);

data.sort((a, b) => b['createdAt'] - a['createdAt']);

// for (var i = 1; i <= 20; i++) {

// var item = 'Notification $i';

for (var i=0; i<data.length; i++){

var item = data[i];

var title = item['title'];

var course = item['course'];

var datetime = DateTime.fromMillisecondsSinceEpoch(item['createdAt']);

var createdAt = DateFormat('EEE, MMMM d, y H:m:s',

'en_US').format(datetime);


widgetList.add(

ListTile(

leading: Icon(Icons.notifications),

// title: Text('Item $i'),

// trailing: Icon(Icons.face),

title: Column(

crossAxisAlignment: CrossAxisAlignment.stretch,

children: <Widget>[

Text(title, style: TextStyle(fontWeight: FontWeight.bold),),

Text(createdAt,

style: TextStyle(fontSize: 10.0, color: Colors.blueGrey),),

],

),

trailing: Text(course.replaceAll(' ', '\n'),

textAlign: TextAlign.right,),

onTap: () {

notificationSelection = item,

Navigator.pushNamed(context, '/notificationView');

},

)

);

}

Variable data is a list for storing notifications downloaded from the Firebase database

(line 5). The for-in loop reads the values from nMap, and add them to data (line 7 ~ 8).

We sort the notifications by creation time (the createdAt field) in descending order (latest

first) using the sort() method with sorting description (an inline function to describe how to

sort) (line 10).

Then, we use the elements of the data list to create ListTile widgets. We also need

notification’s title, created time and course (line 16 ~ 21). The createdAt field stores a timestamp,

we convert it to date and time (line 19 ~ 21).

Then, we put these things into a ListTile widget (line 28 ~ 37).

only the stared lines are changed.

19

return Scaffold(

appBar: AppBar(title: Text('Notifications'),),

body: ListView(

children: widgetList,

padding: EdgeInsets.all(20.0),

),

floatingActionButton: (canCreate)?

FloatingActionButton(

child: Icon(Icons.add),

onPressed: ()=>Navigator.pushNamed(context, '/notificationCreate'),

) : null,

);

}

}

We show a float-action button if the canCreate equals true. The callback function of the float

action button is used to build a new screen for creating a notification (line 53 ~ 57). But, the

code of the notification creation screen is not yet implemented.

6. Save the file and re-run your app. You should see a public notification in the notification list page.

7. Open the Firebase console and add a new entry under the ‘users’ entry with your student ID and

a student role for COMP 7510, same as the ‘xxxxxxxxxx’ entry.

8. Re-open the notification list page on the app. Now, you should see the notifications of COMP 7510

too.

9. Go to the Firebase console again and change your role to administrator or teacher for COMP 7510.

And, re-open the notification list page on the app. You should now see a float action button at the

bottom right corner.

only the stared lines are changed.

20

Updating the Firebase Database

Updating data in the Firebase database is quite simple, we first use a database reference pointing to the

position you want to update. Then, we simply call the methods – push() / set() / remove(), provided by

the database reference to update the data.

Adding New Entry

The push() method creates a database reference object that points to a new position. The set() method

is used to set the value for an existing entry. With these two methods, we can add a new entry to the

database.

For example, we need to post a new notification to everyone. The details of the notification are as follows:

Field Values

Title System Maintenance

Content REACH will not be available on Oct 1, 2018, 00:00 - 02:00.

Course ALL

The following codes can be used to create the new notification:

var ref = dbRef.child('courses/ALL/notifications').push();

var key = ref.key;

ref.set({

'key' : key,

'course' : 'ALL',

'title' : 'System Maintenance',

'content' : 'REACH will not be available on Oct 1, 2018, 00:00 – 02:00',

'createdAt' : DateTime.now().millisecondsSinceEpoch,

'createdBy' : userID,

});

The push() method creates a new empty entry under the position ‘course/ALL/notification’. And, we

assign the returned database reference to variable ref (line 1).

A unique key is generated by the push() method and stored in the database reference (line 3).

The set() method accepts a value (the map), and write it to the new entry (starting from

line 5).

The DateTime.now() method returns the current date and time (line 10). The

millisecondsSinceEpoch property of a DateTime object outputs its time stamp.

21

The following is the example result in the Firebase database:

Changing Existing Entry

To update an existing entry, we only need to use the set() method to write the data to the specific position

directly. For example, the maintenance date is not correct, it should be Oct 2, 2018. The following codes

can be used to correct it:

var ref = dbRef.child(

'courses/ALL/notifications/-LIzR5nFpZ9YPQZ_WrW9/content');

ref.set('REACH will not be available on Oct 2, 2018, 00:00 – 02:00');

Removing Existing Entry

The deletion is very similar to updating an existing entry. We use the remove() method to delete the

entry directly. For example, we need to delete the system maintenance notification because the system

maintenance is canceled. The following codes can be used to delete the notification:

var ref = dbRef.child(

'courses/ALL/notifications/-LIzR5nFpZ9YPQZ_WrW9/content');

ref.remove();

The key is auto-generated

by the push() method.

The sub-entries are written by

the set() method.

22

Creating Notification in Our App

The staff (teachers and administrators) working on a course are able to post notifications to the course.

For example, I am the teacher of COMP7510, I can post notifications to COMP7510. Of course, we need to

have a new user interface for creating notifications. Therefore, we are going to add a new user interface

to our app.

The workflow of the new user interface is quite simple. The build() method builds the interface and

declares a callback function to handle the On-Pressed event of the Send button. When the user presses the

Send button after composing the notification, the callback function will be invoked. The callback function

creates a new entry with the notification details in the Firebase database.

build()

Start

Callback

function

declare

Post Button

Pressed

handle

Firebase database

create

Render

screen

perform

Close screen

perform

TextField widgets

DropdownButton

widget

ListView widgets

23

Widgets

Three new widgets are used in this user interface, the DropdownButton, TextField and Divider widgets.

DropdownButton

The DropdownButton widget provides a dropdown list for selecting items (DropdownMenuItem

widgets). When the user changes the selection of the DropdownButton widget, its On-Changed callback

function will be invoked. And, its value property then stores the selection.

TextField

The TextField widget is used for handling user’s text input. When the user tries something in the

TextField widget, its On-Changed callback function will be invoked. The callback function can be used for

updating internal variables, input validation or so on. In addition, the TextField widget has many

properties for customizing its input mode, such as:

The keyboardType property specifies the keyboard type to multiple lines, email input, number input,

etc.

The decoration property shows the hint text when the text field is empty.

The maxLines property specifies the maximum number of lines. The value equals null that indicates

no limitation. Its default value is 1.

Adding Code

Let’s follow the steps below to create a new user interface:

1. Create a new file named notification_create.dart, and add the following codes to the file:

import 'global.dart';

import 'package:flutter/material.dart';


class NotificationCreationPage extends StatefulWidget {

@override createState() => NotificationCreationState();

}

class NotificationCreationState extends State {

var selectedCourse = roles.keys.first;

var title = '';

var content = '';

@override

Widget build(BuildContext context) {

}

}

Two new classes are declared – NotificationCreationPage (line 4 ~ 6) and

NotificationCreationState (starting from line 8).

24

2. Many things are needed to be done in the build() method. First, prepare a DropdownButton

widget. Add the following code segments to the build() method:

var items = <DropdownMenuItem>[];

for (var k in roles.keys) {

var v = roles[k];

if (['teacher', 'administrator'].contains(v))

items.add(DropdownMenuItem(value: k, child: Text(k)));

}

var ddButton = DropdownButton(

value: selectedCourse,

items: items,

onChanged: (course) => setState(() => selectedCourse = course),

);

we prepare a list of the DropdownMenuItem widgets. These menu items store the course

codes that are retrieved from the global variable roles.

The for-in loop gets the key (course code) and the value (user role) from the roles map (line

3 ~ 8). We check whether the user role equals to teacher or administrator. If yes, it indicates

the user can post notifications for that course. So, we add a menu item with the course code

to the items list.

? We create a DropdownButton widget that contains the recently declared menu items. The

drop-down button is used for selecting a course (line 10 ~ 14).

3. Declare a list that stores different widgets including a Text, DropdownButton, TextField

widgets. Add the codes below to the build() method following the previous code segment:

var widgets = <Widget>[

Text('Post to'),

ddButton,

TextField(

decoration: InputDecoration(hintText: 'Title',),

onChanged: (text) => setState(() => title = text),

),

Divider(color: Colors.transparent,),

TextField(

decoration: InputDecoration(hintText: 'Content',),

keyboardType: TextInputType.multiline,

maxLines: null,

onChanged: (text) => setState(() => content = text),

),

];

The widget list stores a DropdownButton widget and two TextField widgets separated by a

Divider widget.

The TextField widgets are used for the title and content input respectively.

25

4. Declare a scaffold with the widget and return it. Add the codes below to the build() method

following the previous segment:

return Scaffold(

appBar: AppBar(

title: Text('Compose Notification'),

actions: <Widget>[

IconButton(icon: Icon(Icons.send), onPressed: () => post(),),

],

),

body: ListView(

padding: EdgeInsets.all(30.0),

children: <Widget>[Column(children: widgets)],

),

);

An IconButton widget is declared in the app bar. It is used for sending the notification to

the Firebase database. The post() method will be invoked when the user presses the

button.

5. Add the following post() method in the NotificationCreationState class:

void post() {

var ref = dbRef.child('courses/$selectedCourse/notifications').push();

ref.set({

'key' : ref.key,

'course' : selectedCourse,

'title' : title,

'content' : content,

'createdAt' : DateTime.now().millisecondsSinceEpoch,

'createdBy' : userID,

});

Navigator.pop(context);

}

The post() method is used for adding a new notification entry to the Firebase database.

The push() method provides a new position under the

“courses/$selectedCourse/notification” path (line 2). The set() method writes the details of

the notification to the Firebase database, the position ref (line 4 ~ 11).

? The Navigator.pop() statement is used to close the notification create page (line 13).

6. Open the main.dart file and add an import statement for the notification_create.dart file.

import 'package:flutter/material.dart';

26

7. Update the build() method of the MyApp class as follows:

@override

Widget build(BuildContext context) {

return MaterialApp(

home: HomePage(),

routes: <String, WidgetBuilder>{

'/notificationList':

(BuildContext context) => NotificationListPage(),

'/notificationView':

(BuildContext context) => NotificationViewPage(),

'/notificationCreate':

(BuildContext context) => NotificationCreationPage(),

},

);

}

8. Save the files and re-run the app.

9. Go to Firebase console, add an administrator role of COMP 7510 to your user entry.

10. Go to the iOS simulator, re-open the notification list page to refresh your roles. Then, press the “+”

button to create a new notification.

11. Select “COMP 7510” using the drop-down menu button.

12. Input something for the title and content.

13. Press the Send icon button. The notification creation page should be closed then.

14. Check the notification list, there should be a new notification about the system maintenance.

27

Uploading Files to Firebase Storage

The StorageReference.putFile() method provided by Firebase API is used to upload a file to the

Firebase storage. We use a StorageReference object to refer to a destination (position in the Firebase

storage), and use the putFile() method to start a background task (StorageUploadTask) for uploading

a file. With the StorageUploadTask.future.then() method, we declare a callback function for handling

the upload completed event.

Here is an example for uploading a jpeg file:

var fRef = storageRef.child('images/img01.jpg');


var task = fRef.putFile('/~/Documents/myPhoto.jpg');

task.future.then((snapshot) => print(snapshot.downloadUrl.toString()));

Declare the destination using the StorageReference object.

Start uploading the source file to the destination.

Declare a callback function to handle the upload completed event.

Print the download URL of the uploaded file.

Start

Firebase

storage

Set the storage

reference

Call putFile() with a file

StorageUploadTask

callback

Upload

completed

start

declare

file

issue

handle

End

28

Uploading Photos in Our App

We are going to improve the notification creation function so as to allow attaching photos in the

notifications. Two buttons will be added in the app bar for picking a photo from the photo gallery or the

camera. The photos will be shown immediately with cancel buttons. When the user presses the send

button, the photos will be uploaded to the Firebase storage, the notification with the photo download

URLs will be written in the Firebase database.

Let’s follow the steps below:

1. Open the notification_create.dart file and add a new list in the NotificationCreationState class

for storing image files:

class NotificationCreationState extends State {

var images = [];

}

2. Add the following import statement at the top of the file:

import 'package:image_picker/image_picker.dart';

3. Add the attach() method to the NotificationCreationState class:

void attach(source) {

ImagePicker.pickImage(source: source).then((file){

if (file != null)

setState(() => images.add(file));

});

}

The ImagePicker.pickImage() method launches the Camera app or Gallery app for picking a

photo (line 2). If the user cancels the image picking, variable file equals null (line 3). Otherwise,

variable file refers to the selected photo.

In the callback function, the selected photo file will be added to the images list (line 4).

4. In the build() method of the NotificationCreationState class, we add two icon buttons to the

app bar:

return Scaffold(

appBar: AppBar(

title: Text('Compose Notification'),

actions: <Widget>[

IconButton(

icon: Icon(Icons.camera_alt),

onPressed: () => attach(ImageSource.camera),

),

IconButton(

icon: Icon(Icons.photo),

onPressed: () => attach(ImageSource.gallery),

),

IconButton(

icon: Icon(Icons.send),

onPressed: () => post(),

),

],

),

...

);

29

5. The attach() method will be invoked when the On-Pressed event occurs. ImageSource.camera

indicates “photo from camera”; ImageSource.gallery indicates “photo from gallery”.

6. To show the attached photos in the screen, add the following code segments to the build()

method before returning the scaffold:

var width = MediaQuery.of(context).size.width - 120;


for (var f in images) {

widgets.add(Divider(color: Colors.transparent,));

widgets.add(

Row(

mainAxisAlignment: MainAxisAlignment.center,

children: <Widget>[

Image.file(f, width: width),

IconButton(icon: Icon(Icons.cancel), iconSize: 32.0,

onPressed: () => setState(() => images.remove(f)),

)

],

)

);

}

In the for-in loop, we retrieve the image files from the images list.

For each image, we show a transparent divider, the image, and an icon button. These widgets

are added to the widgets list (line 4 ~ 20).

The image.file() method creates an Image widget using the image file (line 12).

The icon buttons are used to remove the corresponding images from the images list (line 15).

30

7. To upload the attached photos to the Firebase storage, add the new code segments to the post()

method before closing the screen:

void post() {

var ref = dbRef.child('courses/$selectedCourse/notifications').push();

var key = ref.key;

ref.set({

'key' : key,

'course' : selectedCourse,

'title' : title,

'content' : content,

'createdAt' : DateTime.now().millisecondsSinceEpoch,

'createdBy' : userID,

});

for (var i=0; i < images.length; i++) {

var fRef = storageRef.child(ref.key + '/$i');

var task = fRef.putFile(images[i]);

task.future.then((snapshot) =>

ref.child('images/$i').set(snapshot.downloadUrl.toString())

);

}

Navigator.pop(context);

}

In the for-in loop, we retrieve the images from the images list. For each image, we declare a

storage reference fRef refers to the destination path (line 15). The putFile() method starts a

upload task (line 18).

In the callback function, we retrieve the download URL of the uploaded file and store the URL

to the notification entry (line 18).

8. Open the /reach/ios/Runner/Info.plist file, add the following keys in the dict tag:

<key>NSPhotoLibraryUsageDescription</key>

<string>This app requires access to the photo library.</string>

<key>NSCameraUsageDescription</key>

<string>This app requires access to the camera.</string>

9. Save the files and re-run your app.

10. Create a new notification. Use the camera button or gallery button to add some photos. Press the

cancel button next to the photo to remove it. Then, press the send button to send the notification.

31

Viewing the Notification Details

Now, we are going to list the user interface – notification view page to the Firebase database. Remember

that the item will be stored in the global variable notificationSelection when the user taps on an item in

the list of the notification list page. The item stores the notification details, and we show them in the

notification view page.

Let’s rewrite the codes of the notification_view.dart as follows:

import 'global.dart';

import 'package:flutter/material.dart';

import 'package:http/http.dart' as http;

import 'dart:async';

import 'package:intl/intl.dart';

class NotificationViewPage extends StatefulWidget {

@override createState() => NotificationViewState();

}

class NotificationViewState extends State {

var images = [];

@override

void initState() {

super.initState();

if (notificationSelection['images'] != null)

for (var url in notificationSelection['images']) {

http.get(url).then((response){

images.add(response.bodyBytes);

if (mounted)

setState((){});

});

}

}


@override

Widget build(BuildContext context) {

var data = notificationSelection;

var title = data['title'];

var course = data['course'];

var content = data['content'];

var createdBy = data['createdBy'];

var datetime = DateTime.fromMillisecondsSinceEpoch(data['createdAt']);

var createdAt = DateFormat('EEE, MMMM d, y H:m:s', 'en_US').format(datetime);

var childWidgets = <Widget>[

Text(course, style: TextStyle(color: Colors.blue),),

Divider(color: Colors.transparent,),

Text(content),

];

var width = MediaQuery.of(context).size.width - 120;

for (var i in images) {

childWidgets.add(Divider(color: Colors.transparent));

childWidgets.add(Image.memory(i, width: width));

}

return Scaffold(

appBar: AppBar(

title: Text(title),

),

body: ListView(

padding: EdgeInsets.all(20.0),

children: <Widget>[

Column(

children: childWidgets,

),

],

),

persistentFooterButtons: <Widget>[

Text('$createdAt by $createdBy'),

(['teacher', 'administrator'].contains(roles[course]))?

IconButton(

icon: Icon(Icons.delete_forever),

onPressed: () => delete(),

):null,

],

);

}

void delete() {

var key = notificationSelection['key'];

var course = notificationSelection['course'];

dbRef.child('courses/$course/notifications/$key').remove();

for (var i = 0; i < images.length; i++)

storageRef.child('$key/$i').delete();

Navigator.pop(context);

}

}

33

Before building the user interface, send http request to the Firebase storage for downloading image

files (line 19 ~ 27). If the file download completes, check whether the user interface is still available

or not before invoking the setState() method.

Variable data stores the current notification, exactly the same as the global variable

notificationSelection (line 30). It is just used to make the variable name shorter.

Read the title, course, content, createdBy, and createAt from the notification (line 32 ~ 38). Then, use

the information to create widgets and store them in the widget list (line 40 ~ 44).

If the images list stores image data, use the data to create Image widgets (line 48 ~ 51).

An icon button is added in the footer for deleting the notification. The icon button will be shown if the

user is the teacher or administrator of the course the notification belongs to (line 69 ~ 72).

A new method delete() is added. It is the callback method for deleting the current notification from

the Firebase database and the corresponding image files from the Firebase storage (line 77 ~ 87).

Exercise

Currently, your app has the functions about notifications only. For the course project, you need to add

additional functions, such as group formation, chatroom, forum, appointment management,

administration tool, etc.

Your tasks are:

1. Discuss with your team members what additional functions should be added.

2. Design the work flows and user interfaces of the new functions.

3. Change the existing screens to match your design

34

Appendix – Running the App in iPhone / iPad

Currently, we use the simulator to run our app. If you want to test your app in your iPhone or iPad, you

can follow the following steps:

1. In IntelliJ, save all files.

2. Use Finder to open “~/Documents/reach/ios/Runner.xcworkspace.

3. In Xcode, select the menu “Xcode” > “Preference…”. Select the “Account” tab and add your apple

ID.

4. In Project Navigator, select the project root – “Runner”. Then, change project: Runner to target:

Runner.

5. In the signing area, change the team to your personal team.

6. Select the menu “Product” > “Destination” > your device.

7. Click the run button of Xcode to run your app.

8. Type the Mac login password and click “Always Allow” if it prompts you about the password for

the keychain.

35

References

[1] Display images from the internet. (n.d.). Retrieved from

https://flutter.io/cookbook/images/network-image/

[2] Firebase for Flutter. (n.d.). Retrieved from

https://codelabs.developers.google.com/codelabs/flutter-firebase/#0

[3] firebase_database | Flutter Package. (n.d.). Retrieved from

https://pub.dartlang.org/packages/firebase_database

[4] firebase_storage | Flutter Package. (n.d.). Retrieved from

https://pub.dartlang.org/packages/firebase_storage

[5] Flutter - Beautiful native apps in record time. (n.d.). Retrieved from https://flutter.io/

[6] A Tour of the Dart Language. (n.d.). Retrieved from

https://www.dartlang.org/guides/language/language-tour


版权所有:编程辅导网 2021 All Rights Reserved 联系方式:QQ:99515681 微信:codinghelp 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。 站长地图

python代写
微信客服:codinghelp