在正文开始之前,我们先组织一下数据,所有Firebase实时数据库的数据都被存储为Json对象。我们可以将该数据库视为云托管Json树,该数据库与SQL数据库不同,没有任何表格或记录。当我们将数据添加至Json树时,它变为现有Json结构中的一个节点。

虽然Firebase实时数据库允许嵌套数据的深度多达32层,然而,当我们提取数据库中某个位置的数据时,也会检索所有子节点。另外,当我们向某用户授予数据库中某个节点的读写访问权时,也会将该节点下所有数据的访问权授予该用户。

在Firebase实时数据库中,数据结构最佳做法是平展数据结构,数据被拆分到不同路径(又称反规范化),可以根据需要通过不同调用有效地下载。即使列表含有成千上万条记录,也可单独提取和显示,从而确保UI 的及时响应和速度。

{
    "users": {
        "1380001": {
            "email": "xingmi@163.com",
            "name": "小明",
            "password": "123456",
            "phone": "1380001"
        },
        "1380002": {
            "..": ".."
        },
        "1380003": {
            "..": ".."
        }
    },
    "chats": {
        "1380001": {
            "1380002": {
                "name": "小红",
                "phone": "1380002",
                "messages": "13800011380002",
                "lastMessage": "电路好像出问题了!",
                "timestamp": 1459361875666,
                "activate": "true"
            },
            "1380003": {
                "name": "小刚",
                "phone": "1380003",
                "messages": "13800011380003",
                "lastMessage": "发现问题,有人把地线和火线接反",
                "timestamp": 1459361875666,
                "activate": "false"
            }
        },
        "1380002": {
            "..": ".."
        },
        "1380003": {
            "..": ".."
        }
    },
    "messages": {
        "13800011380002": {
            "Ph6dARrtdEAUY5PDL2gt": {
                "name": "小红",
                "message": "电路好像出问题了!"
            },
            "x415NpxFZM2CJiBRMCcL": {
                "..": ".."
            }
        },
        "13800011380003": {
            "..": ".."
        },
        "13800031380002": {
            "..": ".."
        }
    }
}

在我们的应用程序中,用户个人资料位于“/users”路径中。现在有“/users/1380001”这个账户,它在“/chats/1380001”下检索所有的会话,再通过“/chats/1380001/messages”的值,在“/messages”中读取这个会话的所有聊天消息。

这里写图片描述

在上一篇文章中,我们实现了聊天列表屏幕的基本UI,在这一篇文本中,会具体实现添加会话的功能。具体实现则是当用户点击右下方的添加按钮时,会进入到添加会话屏幕,因此我们需要在/lib目录下新建一个add_session.dart文件,并添加以下代码。

import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'dart:async';
import 'prompt_wait.dart';

class AddSession extends StatefulWidget {
  AddSession(this.myPhone);
  final String myPhone;

  @override
  State createState() => new _AddSessionState(myPhone);
}

class _AddSessionState extends State<AddSession> {
  _AddSessionState(this._myPhone);
  final String _myPhone;

  @override
  Widget build(BuildContext context) {
    return new SimpleDialog(title: new Text("添加会话"), children: <Widget>[
      new Text("这里用来放一些相关控件");
    ]);
  }
}

在添加完add_session.dart文件后,我们需要把聊天列表屏幕中的添加按钮与添加会话屏幕联系起来。修改group_chat_list.dart文件,添加下面的代码,在用户点击按钮后,就会跳转到添加会话屏幕。这里传递了当前账户的手机号码给添加会话屏幕,使我们能访问对应账户的数据节点,以便添加或修改数据。

//...
import 'add_session.dart';
//...
class _GroupChatListState extends State<GroupChatList> {
  //...
  void _floatingButtonCallback() {
    showDialog<Null>(
        context: context, barrierDismissible: false, child: new AddSession(phone));
  }
  //...
  Widget build(BuildContext context) {
  //...
    return new Scaffold(
        //...
        floatingActionButton: new FloatingActionButton(
            backgroundColor: Colors.orange[800],
            elevation: 0.0,
            onPressed: _floatingButtonCallback,
            child: new Icon(Icons.person_add)));
  }
}

回到add_session.dart文件中,在_AddSessionState中添加一个_findUser方法,用于查找用户是否存在,如果真实存在,则保存这个用户的手机号码和名称,用于给用户展示搜索结果。

class _AddSessionState extends State<AddSession> {
  //...
  final usersReference = FirebaseDatabase.instance.reference().child('users');
  String _searchUsername = "";
  String _searchPhone = "";
  //...
  Future<int> _findUser(String phone) async {
    return await usersReference
        .child(phone)
        .once()
        .then((DataSnapshot onValue) {
      if (onValue.value != null) {
        _searchUsername = onValue.value["name"];
        _searchPhone = onValue.value["phone"];
        return 1;
      } else {
        return 0;
      }
    });
  }
  //...
}

我们还需要一个处理用户点击搜索按钮的事件,在_AddSessionState中添加一个_handleFind方法,用于判断用户输入的手机号码是否格式正确,确定没有格式问题,再调用上面的_findUser方法,在数据库中查找该手机号码的账户,最后根据返回结果做对应操作。

class _AddSessionState extends State<AddSession> {
  //...
  final TextEditingController _phoneController = new TextEditingController();
  //...
  void _handleFind() {
    FocusScope.of(context).requestFocus(new FocusNode());
    if (_phoneController.text.isEmpty) {
      showMessage(context, "手机号码不能为空!");
      return;
    } else if (_phoneController.text.trim() == widget.myPhone) {
      showMessage(context, "这是你的手机号码哦!");
      return;
    } else if (_phoneController.text.trim().length < 7 ||
        _phoneController.text.trim().length > 12) {
      showMessage(context, "手机号码的格式不正确!");
      return;
    }
    showDialog<int>(
            context: context,
            barrierDismissible: false,
            child: new ShowAwait(_findUser(_phoneController.text)))
        .then((int onValue) {
      if (onValue == 0) {
        showMessage(context, "该用户不存在!");
      } else if (onValue == 1) {
        setState(() {});
      }
    });
  }
  //...
}

现在我们在build方法中添加手机号码输入框与搜索按钮,并将搜索按钮的点击事件设置成上面的_handleFind方法。用户一但点击搜索按钮,首先会判断手机号码格式,然后再搜索是否有该用户并保存用户信息。

class _AddSessionState extends State<AddSession> {
  //...
  @override
  Widget build(BuildContext context) {
    return new SimpleDialog(title: new Text("添加会话"), children: <Widget>[
      new Container(
          margin: const EdgeInsets.symmetric(horizontal: 23.0),
          child: new Row(
            children: <Widget>[
              new Flexible(
                  child: new TextField(
                controller: _phoneController,
                keyboardType: TextInputType.phone,
                decoration:
                    new InputDecoration.collapsed(hintText: '点击此处输入手机号码'),
              )),
              new IconButton(
                  icon: new Icon(Icons.search),
                  onPressed: () {
                    _handleFind();
                  }),
            ],
          )),
    ]);
  }
}

上面的代码中,如果手机号码为真实账户,我们会保存账户信息,所以我们还需要将搜索到的账户信息展示给用户。在build方法中添加一个展示用户信息的自定义控件。

class _AddSessionState extends State<AddSession> {
  //...
  @override
  Widget build(BuildContext context) {
    return new SimpleDialog(title: new Text("添加会话"), children: <Widget>[
      //...
      _searchUsername == ""
          ? new Text("")
          : new Container(
              margin: const EdgeInsets.symmetric(horizontal: 23.0),
              child: new Row(
                children: <Widget>[
                  new CircleAvatar(
                      child: new Text(_searchUsername[0]),
                      backgroundColor: Theme.of(context).buttonColor),
                  new Flexible(
                      child: new Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      new Text(
                        "  " + _searchUsername,
                        textScaleFactor: 1.2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      new Text("  " + _searchPhone)
                    ],
                  ))
                ],
              )),
    ]);
  }
}

我们在_AddSessionState中添加一个_addSession方法,用于在“/chats/$user/”下添加一个会话记录。在添加之前,还需要判断是否已经存在该记录,如果已经存在,说明已经添加过这个会话了,这时我们再判断该条记录的activate值,如果是true就说明现在用户的聊天列表中有该条会话,否则说明用户已经删除该会话。

class _AddSessionState extends State<AddSession> {
  //...
  final chatsReference = FirebaseDatabase.instance.reference().child('chats');
  //...
  Future<int> _addSession() async {
    return await chatsReference
        .child('$_myPhone/$_searchPhone')
        .once()
        .then((DataSnapshot onValue) {
      if (onValue.value == null) {
        chatsReference.child('$_myPhone/$_searchPhone').set({
          "name": _searchUsername,
          "phone": _searchPhone,
          "messages": "$_myPhone$_searchPhone",
          "lastMessage": "一起来聊天吧!",
          "activate": "true"
        });
        return 1;
      } else {
        if (onValue.value["activate"] == true) {
          print("跳转到对应的聊天窗口");
          return 0;
        } else {
          print("移除以前的记录,创建一条新记录");
          return 2;
        }
      }
    });
  }
  //...
}

现在添加一个处理用户点击添加按钮的事件,在_AddSessionState中添加一个_handleAppend方法,用于获取_addSession方法的返回值,并做对应的操作。这里关于添加的流程还没有完成,我们会在完成聊天列表屏幕之后再写这些内容。

class _AddSessionState extends State<AddSession> {
  //...
  void _handleAppend() {
    showDialog<int>(
        context: context,
        barrierDismissible: false,
        child: new ShowAwait(_addSession())).then((int onValue) {
      if (onValue == 1) {
        print("会话创建成功,返回到聊天列表屏幕");
      }
    });
  }
  //...
}

最后,我们在build方法中增加一个取消按钮和添加按钮,其中添加按钮在用户没有搜索到好友之前为不可点击状态,搜索到好友之后才是可点击状态。

class _AddSessionState extends State<AddSession> {
  //...
  @override
  Widget build(BuildContext context) {
    return new SimpleDialog(title: new Text("添加会话"), children: <Widget>[
      //...
      new Container(
          margin: const EdgeInsets.only(top: 18.0),
          child: new Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              new RaisedButton(
                elevation: 0.0,
                onPressed: () {
                  Navigator.of(context).pop();
                },
                colorBrightness: Brightness.dark,
                child: const Text('取消'),
              ),
              new RaisedButton(
                elevation: 0.0,
                onPressed: _searchUsername == "" ? null : _handleAppend,
                colorBrightness:
                    _searchUsername == "" ? Brightness.light : Brightness.dark,
                child: const Text('添加'),
              ),
            ],
          ))
    ]);
  }
}

这里写图片描述

大家可以在GitHub上直接查看add_session.dart文件的代码。

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐