Lớp học lập trình Firebase iOS: FriendlyChat

1. Tổng quan

Chào mừng bạn đến với lớp học lập trình FriendlyChat dành cho iOS! Trong lớp học lập trình này, bạn sẽ tìm hiểu cách tạo một ứng dụng trò chuyện theo thời gian thực đầy đủ tính năng cho iOS bằng Firebase và các tính năng hiện đại của Swift như SwiftUI, Swift Concurrency và khung Observation.

Kiến thức bạn sẽ học được

  • Quản lý trạng thái bằng khung Quan sát hiện đại (@Observable).
  • Xác thực người dùng bằng Xác thực Firebase.
  • Đồng bộ hoá tin nhắn trò chuyện theo thời gian thực bằng Cloud Firestore.
  • Lưu trữ và phân phát hình ảnh bằng Cloud Storage cho Firebase.
  • Truy cập vào các API của Firebase bằng các tính năng hiện đại của Swift Concurrency.

Bạn cần có

  • Phiên bản mới nhất của Xcode
  • Trình mô phỏng iOS hoặc thiết bị kiểm thử thực
  • Một Tài khoản Google để tạo và quản lý dự án Firebase

2. Nhận mã mẫu

Sao chép kho lưu trữ GitHub từ dòng lệnh:

$ git clone https://github.com/firebase/codelab-friendlychat-ios

Kho lưu trữ codelab-friendlychat-ios chứa một số dự án mẫu:

  • ios-starter/swift-starter – Dự án Xcode ban đầu chứa bộ khung bố cục SwiftUI và các nhận xét TODO nơi bạn sẽ triển khai các tính năng của ứng dụng bằng Firebase.
  • ios/swift – Dự án Xcode đã hoàn tất với ứng dụng mẫu hoàn chỉnh.

3. Tạo ứng dụng ban đầu

Tạo và chạy ứng dụng khởi động để làm quen với giao diện người dùng:

  1. Trong Finder hoặc Terminal, hãy chuyển đến thư mục ios-starter/swift-starter.
  2. Nhấp đúp vào FriendlyChatSwift.xcodeproj để mở dự án trong Xcode.
  3. Xin lưu ý rằng Xcode tự động phân giải các phần phụ thuộc của Swift Package Manager ở chế độ nền (tải firebase-ios-sdk phiên bản 12 trở lên xuống từ GitHub).
  4. Chọn một Trình mô phỏng iOS (ví dụ: iPhone 17 Pro) rồi nhấp vào nút Run (Chạy) (cmd+R).

Sau vài giây, màn hình đăng nhập FriendlyChat sẽ xuất hiện. Tại thời điểm này, các nút đăng nhập và nguồn cấp dữ liệu tin nhắn chỉ là phần giữ chỗ. Bạn sẽ kết nối Firebase với ứng dụng theo các bước sau.

4. Thiết lập dự án Firebase

Tạo dự án Firebase mới

  1. Đăng nhập vào bảng điều khiển của Firebase bằng Tài khoản Google của bạn.
  2. Nhấp vào nút này để tạo một dự án mới, rồi nhập tên dự án (ví dụ: FriendlyChat).
  3. Nhấp vào Tiếp tục.
  4. Nếu được nhắc, hãy xem xét và chấp nhận các điều khoản của Firebase, rồi nhấp vào Tiếp tục.
  5. (Không bắt buộc) Bật tính năng hỗ trợ của AI trong bảng điều khiển của Firebase (được gọi là "Gemini trong Firebase").
  6. Đối với lớp học lập trình này, bạn không cần Google Analytics, vì vậy hãy tắt lựa chọn Google Analytics.
  7. Nhấp vào Tạo dự án, đợi dự án của bạn được cung cấp, rồi nhấp vào Tiếp tục.

Nâng cấp gói giá Firebase

Để sử dụng Cloud Storage cho Firebase, dự án Firebase của bạn cần phải sử dụng gói giá trả theo mức sử dụng (Blaze), tức là dự án đó được liên kết với một tài khoản thanh toán trên Cloud.

Để nâng cấp dự án lên gói Blaze, hãy làm theo các bước sau:

  1. Trong bảng điều khiển của Firebase, hãy chọn nâng cấp gói.
  2. Chọn gói Blaze. Làm theo hướng dẫn trên màn hình để liên kết một tài khoản thanh toán trên đám mây với dự án của bạn.
    Nếu cần tạo một tài khoản thanh toán trên đám mây trong quá trình nâng cấp này, bạn có thể cần quay lại quy trình nâng cấp trong bảng điều khiển của Firebase để hoàn tất quá trình nâng cấp.

Kết nối ứng dụng iOS

  1. Trên màn hình Tổng quan về dự án, hãy nhấp vào biểu tượng iOS để chạy quy trình thiết lập ứng dụng.
  2. Nhập mã nhận dạng gói:
    com.google.firebase.codelab.FriendlyChatSwift
    
  3. Nhấp vào Đăng ký ứng dụng.

Thêm GoogleService-Info.plist vào dự án Xcode

  1. Nhấp vào Tải GoogleService-Info.plist xuống để lưu tệp cấu hình.
  2. Trong Xcode, hãy kéo tệp GoogleService-Info.plist đã tải xuống vào nhóm chính FriendlyChatSwift trong Project Navigator (Trình điều hướng dự án).
  3. Khi được nhắc, hãy đảm bảo bạn đã chọn Sao chép các mục nếu cần và chọn đích đến là FriendlyChatSwift, sau đó nhấp vào Hoàn tất.

Định cấu hình Firebase trong FriendlyChatSwiftApp.swift

Cuối cùng, hãy khởi động Firebase bằng cấu hình dự án khi ứng dụng SwiftUI của bạn khởi chạy. Mở FriendlyChatSwiftApp.swift rồi thay thế trình giữ chỗ TODO bằng import FirebaseCoreFirebaseApp.configure():

import SwiftUI
import FirebaseCore

@main
struct FriendlyChatSwiftApp: App {
  init() {
    FirebaseApp.configure()
  }

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

5. Xác thực người dùng

Định cấu hình quy trình xác thực trong bảng điều khiển của Firebase

Trước khi có thể xác thực người dùng, trước tiên, bạn phải bật nhà cung cấp dịch vụ xác thực mà bạn chọn trong bảng điều khiển của Firebase. Hướng dẫn này sử dụng phương thức xác thực Email/Mật khẩu:

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến phần Bảo mật > Xác thực, rồi nhấp vào Bắt đầu.
  2. Chọn thẻ Phương thức đăng nhập.
  3. Nhấp vào Email/Mật khẩu, nhấp vào nút bật/tắt Bật rồi nhấp vào Lưu.

Bảo mật Cloud Firestore bằng các quy tắc xác thực

Tiếp theo, yêu cầu người dùng xác thực trước khi đọc hoặc ghi tin nhắn:

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến Cơ sở dữ liệu và bộ nhớ > Cơ sở dữ liệu Firestore, rồi nhấp vào Tạo cơ sở dữ liệu.
  2. Chọn một vị trí rồi nhấp vào Tiếp theo.
  3. Chọn Bắt đầu ở chế độ thử nghiệm rồi nhấp vào Tạo.
  4. Chọn thẻ Quy tắc rồi cập nhật các quy tắc bảo mật thành:
    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /messages/{messageId} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    Các quy tắc này cho phép mọi người dùng đã xác thực đọc hoặc ghi tin nhắn vào cơ sở dữ liệu, phù hợp cho mục đích học tập. Tìm hiểu thêm về các quy tắc bảo mật trong tài liệu về quy tắc bảo mật.
  5. Nhấp vào Xuất bản.

Theo dõi trạng thái xác thực trong UserViewModel.swift

Trong SwiftUI, việc quan sát trạng thái Xác thực Firebase một cách phản ứng giúp giao diện người dùng của bạn chuyển đổi mượt mà giữa màn hình đăng nhập và giao diện trò chuyện.

Mở ViewModels/UserViewModel.swift và thay thế TODO trong init() để quan sát các thay đổi về trạng thái uỷ quyền bằng cách sử dụng authStateChanges AsyncSequence:

  init() {
    authTask = Task {
      for await user in Auth.auth().authStateChanges {
        self.user = user
      }
    }
  }

Triển khai tính năng đăng nhập, đăng ký và đăng xuất bằng Swift Concurrency

Trong UserViewModel.swift, hãy thay thế các nhận xét TODO còn lại bằng các phương thức xác thực async/await hiện đại:

  func signIn(email: String, password: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      try await Auth.auth().signIn(withEmail: email, password: password)
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signUp(email: String, password: String, displayName: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      let result =
        try await Auth.auth().createUser(withEmail: email, password: password)
      let changeRequest = result.user.createProfileChangeRequest()
      changeRequest.displayName = displayName.isEmpty ? email : displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func updateDisplayName(_ displayName: String) async {
    guard let currentUser = Auth.auth().currentUser else { return }
    do {
      let changeRequest = currentUser.createProfileChangeRequest()
      changeRequest.displayName = displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signOut() {
    do {
      try Auth.auth().signOut()
    } catch {
      showError(error.localizedDescription)
    }
  }

Kiểm thử quy trình xác thực người dùng

  1. Nhấp vào nút Run (Chạy) trong Xcode.
  2. Trong trình mô phỏng ứng dụng, hãy nhấp vào Đăng ký, nhập Tên hiển thị, Email và Mật khẩu, rồi nhấn vào ĐĂNG KÝ.
  3. Bạn sẽ được tự động xác thực và chuyển đến màn hình nhắn tin trống!

6. Đọc từ Cloud Firestore

Thêm tin nhắn mẫu trong bảng điều khiển của Firebase

Điền dữ liệu vào cơ sở dữ liệu bằng các thông báo mẫu:

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến Cơ sở dữ liệu Firestore rồi chọn thẻ Dữ liệu.
  2. Nhấp vào Bắt đầu thu thập.
  3. Nhập messages cho Mã nhận dạng bộ sưu tập, rồi nhấp vào Tiếp theo.
  4. Để Mã nhận dạng tài liệu ở chế độ tự động tạo (hoặc nhấp vào Tự động tạo mã nhận dạng).
  5. Thêm các trường sau vào tài liệu:
    • text (loại: string, giá trị: Hello)
    • displayName (loại: string, giá trị: anonymous)
  6. Nhấp vào Lưu.
  7. Bạn có thể nhấp vào Thêm tài liệu để thêm nhiều tin nhắn mẫu hơn.

Đồng bộ hoá thư trong FriendlyMessageViewModel.swift

Trong SwiftUI, mô hình @Observable điều khiển các bản cập nhật giao diện người dùng khai báo. Mở ViewModels/FriendlyMessageViewModel.swift rồi triển khai startListening()stopListening() bằng snapshots AsyncSequence:

  func startListening() {
    stopListening()
    listenerTask = Task {
      let db = Firestore.firestore()
      do {
        for try await snapshot in db.collection("messages").snapshots {
          self.messages = snapshot.documents.compactMap { document in
            try? document.data(as: FriendlyMessage.self)
          }
        }
      } catch {
        print("Error listening for messages: \(error)")
      }
    }
  }

  func stopListening() {
    listenerTask?.cancel()
    listenerTask = nil
    messages.removeAll()
  }

Cách SwiftUI kết xuất và tự động cuộn tin nhắn

Trong Views/ContentView.swift, hãy lưu ý cách ScrollViewReaderLazyVStack hiển thị bong bóng trò chuyện và tự động cuộn đến tin nhắn mới nhất bất cứ khi nào messages.count thay đổi:

ScrollViewReader { scrollViewReader in
  ScrollView {
    LazyVStack(spacing: 12) {
      ForEach(messageViewModel.messages) { message in
        FriendlyMessageView(friendlyMessage: message)
          .id(message.id)
      }
    }
    .padding(.horizontal)
    .onChange(of: messageViewModel.messages.count) { _, count in
      guard count > 0,
            let lastId = messageViewModel.messages.last?.id else { return }
      withAnimation(.easeInOut) {
        scrollViewReader.scrollTo(lastId, anchor: .bottom)
      }
    }
  }
}

Bằng cách liên kết trạng thái cơ sở dữ liệu trực tiếp với trạng thái giao diện người dùng thông qua mô hình thành phần hiển thị, SwiftUI cho phép mã ứng dụng ánh xạ các hiệu ứng chuyển đổi một cách rõ ràng sang các ảnh động. Trong bước tiếp theo, bạn sẽ ghi dữ liệu vào cơ sở dữ liệu từ ứng dụng để quan sát hiệu ứng động này trong thực tế.

7. Gửi tin nhắn

Triển khai sendMessage bằng Swift Concurrency

Khi bạn thêm một tài liệu vào một bộ sưu tập bằng cách sử dụng addDocument(), Cloud Firestore sẽ tạo một mã nhận dạng duy nhất cho mỗi tin nhắn trò chuyện.

Trong ViewModels/FriendlyMessageViewModel.swift, hãy thay thế TODO trong sendMessage(text:imageUrl:) bằng phương thức async throws:

  func sendMessage(text: String?, imageUrl: String?) async throws {
    guard let currentUser = Auth.auth().currentUser else { return }
    let message = FriendlyMessage(
      text: text,
      displayName: currentUser.displayName ?? currentUser.email ?? "Anonymous",
      imageUrl: imageUrl,
      userId: currentUser.uid
    )
    let db = Firestore.firestore()
    _ = try db.collection("messages").addDocument(from: message)
  }

Thử gửi tin nhắn

  1. Nhấp vào nút Run (Chạy) trong Xcode.
  2. Đăng nhập vào tài khoản của bạn.
  3. Nhập tin nhắn vào trường văn bản ở dưới cùng rồi nhấn vào biểu tượng Gửi.
  4. Xem thông báo xuất hiện ngay lập tức trong trình mô phỏng!

8. Lưu trữ và nhận hình ảnh

Thiết lập Cloud Storage cho Firebase

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến Cơ sở dữ liệu và bộ nhớ > Bộ nhớ, rồi nhấp vào Bắt đầu.
  2. Chọn Bắt đầu ở chế độ kiểm thử rồi nhấp vào Tiếp theo.
  3. Chấp nhận vị trí lưu trữ mặc định rồi nhấp vào Xong.

Chọn hình ảnh gốc bằng SwiftUI PhotosPicker

SwiftUI hiện đại (iOS 16 trở lên) cung cấp tính năng chọn ảnh gốc thông qua PhotosPicker từ import PhotosUI – không cần cầu nối uikit delegate hoặc trình bao bọc UIImagePickerController!

Trong Views/FooterView.swift, hãy quan sát cách PhotosPicker liên kết các hình ảnh đã chọn:

PhotosPicker(selection: $selectedItem, matching: .images) {
  Image(systemName: "photo.on.rectangle.angled")
    .font(.system(size: 26))
    .foregroundStyle(.blue)
    .accessibilityLabel("Select photo")
}
.onChange(of: selectedItem) { _, newItem in
  Task {
    if let data = try? await newItem?.loadTransferable(type: Data.self) {
      await uploadAndSendImage(data: data)
    }
  }
}

Triển khai tính năng tải hình ảnh lên

Khi một bức ảnh được chọn, hãy tải dữ liệu hình ảnh lên Cloud Storage và lưu URL tải công khai trong Cloud Firestore.

Trong Views/FooterView.swift, hãy thay thế TODO trong uploadAndSendImage(data:):

  private func uploadAndSendImage(data: Data) async {
    guard let uid = Auth.auth().currentUser?.uid else { return }
    isUploading = true
    defer {
      isUploading = false
      selectedItem = nil
    }
    do {
      let filename = "\(uid)/\(UUID().uuidString).jpg"
      let storageRef =
        Storage.storage().reference().child("images").child(filename)
      let metadata = StorageMetadata()
      metadata.contentType = "image/jpeg"

      _ = try await storageRef.putDataAsync(data, metadata: metadata)
      let downloadURL = try await storageRef.downloadURL()
      try await viewModel.sendMessage(
        text: nil, imageUrl: downloadURL.absoluteString)
    } catch {
      print("Error uploading image: \(error.localizedDescription)")
    }
  }

Chia sẻ hình ảnh bằng Cloud Storage

Trong Views/FriendlyMessageImageView.swift, URL hình ảnh được tải không đồng bộ bằng cách sử dụng Swift Concurrency và Cloud Storage SDK:

  • URL tải xuống HTTP/HTTPS tiêu chuẩn được truy xuất không đồng bộ thông qua URLSession.
  • URL Google Cloud Storage (ví dụ: gs://...) được tìm nạp bằng Storage.storage().reference(forURL:).

Kiểm thử tin nhắn hình ảnh

  1. Nhấp vào nút Run (Chạy) trong Xcode.
  2. Đăng nhập vào tài khoản của bạn.
  3. Nhấn vào biểu tượng Ảnh, chọn một hình ảnh trong thư viện rồi xem hình ảnh đó tải lên và xuất hiện trong cuộc trò chuyện!

9. Kết luận

Xin chúc mừng! Bạn đã tạo thành công một ứng dụng trò chuyện theo thời gian thực trên iOS bằng Swift và Firebase!

Kiến thức bạn học được

  • Trình quản lý gói Swift (SPM) Tích hợp Xcode cho firebase-ios-sdk.
  • Bố cục khai báo SwiftUI, PhotosPickerScrollViewReader tự động cuộn.
  • Tính năng Xử lý đồng thời trong Swift (async/await) trên các dịch vụ Xác thực, Cloud Firestore và Cloud Storage.
  • Xác thực bằng email/mật khẩu và xác thực ẩn danh bằng tính năng theo dõi trạng thái phản ứng.
  • Tính năng nghe ảnh chụp nhanh theo thời gian thực và ghi mã nhận dạng tự động của Cloud Firestore.
  • Tải tệp nhị phân lên và URL tải công khai xuống của Cloud Storage cho Firebase.

Tìm hiểu thêm