返回博客首页
← 所有文章

节日快乐:使用 Firebase、Angular 2 和 NativeScript 创建移动应用

2016 年 12 月 15 日 — 作者:Jen Looper

观看我们网络研讨会 "NativeScript on Fire(base) 🔥" 的录制视频
NativeScript、Firebase 和 Angular 2 的强大组合可以将您的应用程序构建提升到一个新的高度,尤其是在节日期间,您需要加快应用程序开发速度并满足家人送礼的需求!适逢其时,我很高兴向您展示(看看我做了什么 包装礼物)如何利用 Eddy Verbruggen 著名的 NativeScript-Firebase 插件 中的多个元素在您的 Angular 2 驱动的 NativeScript 应用程序中利用 Firebase。

在本教程中,我将向您展示如何在 NativeScript 应用程序中使用四个流行的 Firebase 元素:使用登录和注册例程进行身份验证;用于数据存储和实时更新的数据库;用于远程更改应用程序的远程配置;以及用于保存照片的存储。为此,我决定重写我的 Giftler 应用程序,该应用程序最初是用 Ionic 编写的。

在开始之前,我建议您在开始项目之前阅读 文档,并确保满足一些先决条件。

    1

    在 Firebase 控制台中创建项目

    2

    添加 iOS 应用
     3

    添加 Android 应用

    安装依赖项

    我构建了 Giftler 作为经过身份验证的 NativeScript 应用程序的示例,用户可以在其中列出他们希望在假期收到的礼物,包括照片和文字描述。目前,此应用程序在 iOS 和 Android 上执行以下操作

    • 允许登录和注销、注册以及“忘记密码”例程
    • 允许用户将礼品项目输入列表
    • 允许用户从列表中删除项目
    • 允许用户通过添加描述和照片单独编辑列表中的项目
    • 提供来自 Firebase 中远程配置服务的邮件,可以在后端快速更改

    现在,分叉 Giftler 源代码,这是一个完整且功能齐全的应用程序。克隆应用程序后,替换在创建应用程序时下载的应用程序当前的基于 Firebase 的文件。

    • 在 /app/App_Resources/Android 文件夹中,放置从 Firebase 下载的 google.services.json 文件。
    • 同样,在 /app/App_Resources/iOS 文件夹中,放置从 Firebase 下载的 GoogleService-Info.plist 文件。
    这些文件对于在您的应用程序中初始化 Firebase 并将其连接到相关的外部服务是必要的。

    现在,让我们看一下此应用程序根目录下的 package.json。它包含您将在此应用程序中使用的插件。我想提请您注意面向 NativeScript 的插件。
    "nativescript-angular": "1.2.0",
    "nativescript-camera": "^0.0.8",
    "nativescript-iqkeyboardmanager": "^1.0.1",
    "nativescript-plugin-firebase": "^3.8.4",
    "nativescript-theme-core": "^1.0.2",
    

    NativeScript-Angular 插件是 NativeScript 对 Angular 的集成。Camera 插件使管理摄像头变得更加容易。IQKeyboardManager 是一个特定于 iOS 的插件,用于处理 iOS 上难以捉摸的键盘。Theme 插件是一种在无需完全自定义应用程序外观的情况下向应用程序添加默认样式的好方法。最后,此应用程序中最重要的插件是 Firebase 插件。

    在依赖项到位且插件准备就绪后,您可以构建您的应用程序以创建包含 iOS 和 Android 特定代码的 platforms 文件夹,并初始化 Firebase 插件以及其余基于 npm 的插件。使用 NativeScript CLI,导航到克隆应用程序的根目录并键入 tns run ios 或 tns run android。这将启动插件构建例程,特别是您将看到 Firebase 插件的各个部分开始安装。运行的安装脚本将提示您安装几个元素以集成到各种 Firebase 服务中。目前,我们将选择除 Messaging 和社交身份验证之外的所有内容。一个很棒的功能是 firebase.nativescript.json 文件安装在应用程序的根目录,因此如果您以后需要安装插件的新部分,您可以编辑该文件并重新安装插件。

    giftler-plugin-install

    此时,如果您运行 tns livesync ios --watch 或 tns livesync android --watch 以查看应用程序在模拟器上运行并监视更改,您将看到应用程序正在运行并准备接受您的新登录。但是,在初始化登录之前,请确保 Firebase 通过在 Firebase 控制台的身份验证选项卡中启用此功能来处理电子邮件/密码类型登录。

    4

    让我们稍微看一下幕后发生了什么。在登录 Firebase 之前,您需要初始化已安装的 Firebase 服务。在 app/main.ts 中,有一些有趣的部分。

    // this import should be first in order to load some required settings (like globals and reflect-metadata)
    import { platformNativeScriptDynamic } from "nativescript-angular/platform";
    
    import { AppModule } from "./app.module";
    import { BackendService } from "./services/backend.service";
    
    import firebase = require("nativescript-plugin-firebase");
    
    firebase.init({
      //persist should be set to false as otherwise numbers aren't returned during livesync
      persist: false,
      storageBucket: 'gs://giftler-f48c4.appspot.com',
      onAuthStateChanged: (data: any) => {
        console.log(JSON.stringify(data))
        if (data.loggedIn) {
          BackendService.token = data.user.uid;
        }
        else {
          BackendService.token = "";
        }
      }
    }).then(
      function (instance) {
        console.log("firebase.init done");
      },
      function (error) {
        console.log("firebase.init error: " + error);
      }
      );
    platformNativeScriptDynamic().bootstrapModule(AppModule);
    

    首先,我们从插件导入 firebase,然后调用 .init()。编辑 storageBucket 属性以反映 Firebase 控制台的存储选项卡中的值。


    5

    现在,您的应用程序已自定义到您自己的 Firebase 帐户,您应该能够注册新用户并在应用程序中登录。您可以编辑 app/login/login.component.ts 文件中的 user.email 和 password 变量,将默认登录凭据从 [email protected] 更改为您自己的登录名和密码(如果您愿意)。

    6


    iOS 和 Android 登录屏幕

    • 注意:您应该能够立即在 iOS 上使用 Xcode 模拟器模拟您的应用程序。但是,在 Android 上,您可能需要执行一些步骤才能使应用程序安装在模拟器上,包括启用 Google 服务。这是一个关于如何在 Genymotion(我首选的 Android 模拟器)中执行此操作的教程

    代码结构和身份验证

    Angular 2 设计模式要求您将代码模块化,因此我们将使用以下代码结构:
    登录
      login.component.ts

      login.html
      login.module.ts
      login.routes.ts
    列表 …
    列表详情 …
    模型
      gift.model.ts
       user.model.ts
      index.ts
    服务
      backend.service.ts
      firebase.service.ts
      utils.service.ts
      index.ts
    app.component.ts
    app.css
    app.module.ts
    app.routes.ts
    auth-guard.service.ts
    main.ts

    我想提请您注意 Firebase 身份验证与 Angular 2 auth-guard.service 的工作方式。如上所述,当 Firebase 在您的应用程序的 app/main.ts 中初始化时,会调用 onAuthStateChanged 函数。

    
    onAuthStateChanged: (data: any) => {
        console.log(JSON.stringify(data))
        if (data.loggedIn) {
          BackendService.token = data.user.uid;
        }
        else {
          BackendService.token = "";
        }
      }
    

    应用程序启动时,检查控制台以获取 Firebase 返回的字符串化数据。如果此用户被标记为已登录,我们将简单地设置一个令牌,该令牌是 Firebase 发送回的 userId。我们将使用 NativeScript 应用程序设置模块(其功能类似于 localStorage)来使此 userId 可用并将其与我们创建的数据关联。此令牌和使用它的身份验证测试(在 app/services/backend.service.ts 文件中管理)可用于 app/auth-guard.service.ts 文件。auth-guard 文件提供了一种管理已登录和已注销应用程序状态的简洁方法。

    AuthGuard 类实现了 Angular Router 模块中的 CanActivate 接口。

    export class AuthGuard implements CanActivate {
      constructor(private router: Router) { }
    
      canActivate() {
        if (BackendService.isLoggedIn()) {
          return true;
        }
        else {
          this.router.navigate(["/login"]);
          return false;
        }
      }
    

    从本质上讲,如果在上述登录例程期间设置了令牌,并且 BackendService.isLoggedIn 函数返回 true,则允许应用程序导航到默认路由(即我们的愿望清单);否则,用户将被送回登录页面。

    const listRoutes: Routes = [
      { path: "", component: ListComponent, canActivate: [AuthGuard] },
    ];
    

    现在您已初始化基于 Firebase 的 NativeScript 应用程序,让我们了解如何使用数据填充它并使用 Firebase 的惊人实时功能来监视数据库的更新。

    制作您的清单,检查两次


    7

    从 app/list/list.html 开始,它是愿望清单的基础,您将看到一个文本字段和一个空白列表。继续,告诉圣诞老人您想要什么!这些项目会发送到数据库并实时添加到您的列表中。让我们看看这是如何完成的。

    首先,请注意,在 app/list/list.component.ts 中,我们设置了一个可观察对象来保存礼物列表:

    public gifts$: Observable;

    然后,我们在组件初始化时从数据库填充该列表。

    ngOnInit(){
      this.gifts$ = this.firebaseService.getMyWishList();
    }
    

    在 firebaseService 文件中,事情变得有趣起来。请注意此函数添加侦听器并返回 rxjs 可观察对象的方式,检查 Firebase 数据库中 Gifts 集合的更改。

    getMyWishList(): Observable {
      return new Observable((observer: any) => {
          let path = 'Gifts';
            let onValueEvent = (snapshot: any) => {
              this.ngZone.run(() => {
                let results = this.handleSnapshot(snapshot.value);
                console.log(JSON.stringify(results))
                 observer.next(results);
              });
            };
            firebase.addValueEventListener(onValueEvent, `/${path}`);
        }).share();
    }
    

    此查询的结果在下面的 handleSnapshot 函数中处理,该函数按用户过滤数据,填充 _allItems 数组。

    handleSnapshot(data: any) {
        //empty array, then refill and filter
        this._allItems = [];
        if (data) {
          for (let id in data) {
            let result = (Object).assign({id: id}, data[id]);
            if(BackendService.token === result.UID){
              this._allItems.push(result);
            }
          }
          this.publishUpdates();
        }
        return this._allItems;
      }
    

    最后,调用 publishUpdates,它按日期对数据进行排序,以便首先显示较新的项目。

    publishUpdates() {
        // here, we sort must emit a *new* value (immutability!)
        this._allItems.sort(function(a, b){
            if(a.date < b.date) return -1;
            if(a.date > b.date) return 1;
          return 0;
        })
        this.items.next([...this._allItems]);
      }
    

    数据填充您的 $gifts 可观察对象后,您可以编辑和删除其元素,它将由侦听器处理并相应地更新前端。请注意,getMyWishList 方法的 onValueEvent 函数包括使用 ngZone,它确保尽管数据更新是异步发生的,但 UI 会相应地更新。可以在此处找到关于 NativeScript 应用程序中 ngZone 的良好概述。

    来自圣诞老人的远程配置消息

    8

    Firebase 服务的另一个很酷的部分包括“远程配置”,这是一种从 Firebase 后端提供应用程序更新的方法。您可以使用远程配置在应用程序中打开和关闭功能、进行 UI 更改或发送来自圣诞老人的消息,这就是我们要做的!

    在 app/list/list.html 中,您会找到一个消息框。

    <Label class="gold card" textWrap="true" [text]="message$ | async"></Label>

    message$ 可观察对象与数据列表的构建方式大致相同;在这种情况下,每次应用程序新初始化时都会获取更改。

    ngOnInit(){
      this.message$ = this.firebaseService.getMyMessage();
    }
    

    魔法发生在服务层(app/services/firebase.service.ts)中。

    getMyMessage(): Observable{
        return new Observable((observer:any) => {
          firebase.getRemoteConfig({
          developerMode: false,
          cacheExpirationSeconds: 300,
          properties: [{
          key: "message",
          default: "Happy Holidays!"
        }]
      }).then(
            function (result) {
              console.log("Fetched at " + result.lastFetch + (result.throttled ? " (throttled)" : ""));
              for (let entry in result.properties)
                {
                  observer.next(result.properties[entry]);
                }
            }
        );
      }).share();
    }
    

    9


    根据需要发布新消息!

    • 注意:反复修改远程配置可能会导致 Firebase 实例被限制,因此请谨慎开发。

    拍照!

    10

    我认为,此项目中更有趣的部分之一是能够拍摄您选择的礼物的照片并将其存储在 Firebase 存储中。我利用了上面提到的 Camera 插件,它使管理硬件变得更容易。首先,请确保您的应用程序可以通过在 app/list-detail/list-detail.component.ts 中的 ngOnInit() 方法中设置权限来访问设备摄像头。



    ngOnInit() {
       camera.requestPermissions();
       ...
      }
    

    当用户在详情屏幕点击“照片”按钮时,一系列事件开始。首先,

    takePhoto() {
      let options = {
                width: 300,
                height: 300,
                keepAspectRatio: true,
                saveToGallery: true
            };
        camera.takePicture(options)
            .then(imageAsset => {
                imageSource.fromAsset(imageAsset).then(res => {
                    this.image = res;
                    //save the source image to a file, then send that file path to firebase
                    this.saveToFile(this.image);
                })
            }).catch(function (err) {
                console.log("Error -> " + err.message);
            });
    }
    

    相机拍摄照片,然后该照片被存储为 imageAsset 并显示在屏幕上。然后,图像使用日期戳命名并保存到本地文件。该路径将保留以备将来使用。

    saveToFile(res){
      let imgsrc = res;
            this.imagePath = this.utilsService.documentsPath(`photo-${Date.now()}.png`);
            imgsrc.saveToFile(this.imagePath, enums.ImageFormat.png);
    }
    

    一旦按下“保存”按钮,此图像将通过其本地路径发送到 Firebase 并保存在存储模块中。其在 Firebase 中的完整路径将返回到应用程序并存储在 /Gifts 数据库集合中。

    editGift(id: string){
      if(this.image){
        //upload the file, then save all
        this.firebaseService.uploadFile(this.imagePath).then((uploadedFile: any) => {
              this.uploadedImageName = uploadedFile.name;
              //get downloadURL and store it as a full path;
              this.firebaseService.getDownloadUrl(this.uploadedImageName).then((downloadUrl: string) => {
                this.firebaseService.editGift(id,this.description,downloadUrl).then((result:any) => {
                  alert(result)
                }, (error: any) => {
                    alert(error);
                });
              })
            }, (error: any) => {
              alert('File upload error: ' + error);
            });
      }
      else {
        //just edit the description
        this.firebaseService.editDescription(id,this.description).then((result:any) => {
            alert(result)
        }, (error: any) => {
            alert(error);
        });
      }
    }
    

    这一系列事件看起来很复杂,但它归结为 Firebase 服务文件中的几行代码。

    
    uploadFile(localPath: string, file?: any): Promise {
          let filename = this.utils.getFilename(localPath);
          let remotePath = `${filename}`;
          return firebase.uploadFile({
            remoteFullPath: remotePath,
            localFullPath: localPath,
            onProgress: function(status) {
                console.log("Uploaded fraction: " + status.fractionCompleted);
                console.log("Percentage complete: " + status.percentageCompleted);
            }
          });
      }
    
    getDownloadUrl(remoteFilePath: string): Promise {
          return firebase.getDownloadUrl({
            remoteFullPath: remoteFilePath})
          .then(
            function (url:string) {
              return url;
            },
            function (errorMessage:any) {
              console.log(errorMessage);
            });
    }
    editGift(id:string, description: string, imagepath: string){ this.publishUpdates(); return firebase.update("/Gifts/"+id+"",{ description: description, imagepath: imagepath}) .then( function (result:any) { return 'You have successfully edited this gift!'; }, function (errorMessage:any) { console.log(errorMessage); }); }

    最终结果是一种很好的方式来捕获心愿单礼物的照片和描述。再也不用担心圣诞老人不知道要买哪款 Kylie 眼线笔了。通过结合 NativeScript 和 Angular 的强大功能,您可以在几分钟内创建原生 iOS 和 Android 应用程序。通过添加 Firebase,您可以获得一种强大的方式来存储应用程序的用户、图像和数据,以及一种跨设备实时更新数据的方式。酷吧?它看起来像这样

    final

    我们正在朝着创建一个可靠的心愿单管理应用程序迈进!剩下的就是找到通知圣诞老人我们愿望的最佳方式 - Mailgun 电子邮件集成或使用推送通知将是下一个明显的途径。在此期间,祝您节日快乐,希望您在使用 Firebase 创建很棒的 NativeScript 应用程序时玩得开心!