флаттер|файрбейз. файрбейз-аутентификация | сбой устройства

Вопрос или проблема

это мой teacher_acc.dart;

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class TeacherAcc extends StatelessWidget {
  const TeacherAcc({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
          .collection('pending_teachers')
          .where('status', isEqualTo: 'pending')
          .snapshots(),
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return Center(child: Text('Ошибка: ${snapshot.error}'));
        }

        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }

        final teachers = snapshot.data?.docs ?? [];

        if (teachers.isEmpty) {
          return const Center(child: Text('Нет ожидающих учителей'));
        }

        return ListView.separated(
          padding: const EdgeInsets.all(16),
          itemCount: teachers.length,
          separatorBuilder: (context, index) => const SizedBox(height: 16),
          itemBuilder: (context, index) {
            final teacherData = teachers[index].data() as Map<String, dynamic>;
            return TeacherInfoCard(
              teacherData: teacherData,
              onConfirm: () => _confirmTeacher(context, teachers[index].id, teacherData),
            );
          },
        );
      },
    );
  }

  Future<void> _confirmTeacher(BuildContext context, String docId, Map<String, dynamic> teacherData) async {
    try {
      await FirestoreOperations.transferTeacherToConfirmed(docId, teacherData);
      _showSuccessMessage(context, 'Учитель успешно подтверждён!');
    } catch (e) {
      print('Ошибка при подтверждении учителя: $e');
      _showErrorMessage(context, e.toString());
    }
  }

  void _showSuccessMessage(BuildContext context, String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  void _showErrorMessage(BuildContext context, String error) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('Ошибка: $error')),
    );
  }
}

class TeacherInfoCard extends StatelessWidget {
  final Map<String, dynamic> teacherData;
  final VoidCallback onConfirm;

  const TeacherInfoCard({
    super.key,
    required this.teacherData,
    required this.onConfirm,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      margin: const EdgeInsets.only(bottom: 16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              '${teacherData['firstName'] ?? ''} ${teacherData['middleName'] ?? ''} ${teacherData['lastName'] ?? ''}',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 8),
            _buildInfoRow(Icons.email, teacherData['email'] ?? 'N/A'),
            _buildInfoRow(Icons.phone, teacherData['phoneNumber'] ?? 'N/A'),
            _buildInfoRow(Icons.school, teacherData['school'] ?? 'N/A'),
            _buildInfoRow(Icons.class_, teacherData['section'] ?? 'N/A'),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                ElevatedButton(
                  onPressed: onConfirm,
                  child: const Text('Подтвердить'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(IconData icon, String text) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          Icon(icon, size: 20),
          const SizedBox(width: 8),
          Expanded(child: Text(text)),
        ],
      ),
    );
  }
}

class FirestoreOperations {
  static Future<void> transferTeacherToConfirmed(String docId, Map<String, dynamic> teacherData) async {
    final FirebaseFirestore firestore = FirebaseFirestore.instance;

    try {
      await firestore.runTransaction((transaction) async {
        // Добавить учителя в коллекцию confirmed_teachers
        final confirmedTeacherRef = firestore.collection('confirmed_teachers').doc(teacherData['userId'] ?? docId);
        transaction.set(confirmedTeacherRef, {
          'firstName': teacherData['firstName'] ?? '',
          'middleName': teacherData['middleName'] ?? '',
          'lastName': teacherData['lastName'] ?? '',
          'email': teacherData['email'] ?? '',
          'phoneNumber': teacherData['phoneNumber'] ?? '',
          'school': teacherData['school'] ?? '',
          'section': teacherData['section'] ?? '',
          'confirmedAt': FieldValue.serverTimestamp(),
        });

        // Удалить документ ожидающего учителя
        final pendingTeacherRef = firestore.collection('pending_teachers').doc(docId);
        transaction.delete(pendingTeacherRef);
      });
    } catch (e) {
      print('Ошибка при перемещении учителя в FirestoreOperations.transferTeacherToConfirmed(): $e');
      rethrow;
    }
  }
}

а это соответствующий auth_controller.dart:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter/material.dart';
import 'package:smartkidstracker/src/main_screen.dart';
import 'package:firebase_core/firebase_core.dart';

class AuthController {
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Future<void> initializeFirebase() async {
    try {
      await Firebase.initializeApp();
      print('Firebase инициализирован');
    } catch (e) {
      print('Ошибка при инициализации Firebase: $e');
    }
  }

  Future<Map<String, dynamic>> signInWithEmailAndPassword(String email, String password) async {
    try {
      print('Попытка входа с электронной почтой: $email');
      final UserCredential userCredential = await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );

      final User? user = userCredential.user;

      if (user != null) {
        print('Пользователь успешно аутентифицирован. UID: ${user.uid}');

        // Проверка коллекции пользователей
        DocumentSnapshot userDoc = await _firestore.collection('users').doc(user.uid).get();

        if (userDoc.exists) {
          print('Документ пользователя найден в коллекции пользователей');
          final userData = userDoc.data() as Map<String, dynamic>;
          final String role = userData['role'] ?? '';

          print('Роль пользователя: $role'); // Добавьте эту строку для отладки

          if (role.toLowerCase() == 'admin') {
            print('Пользователь администратор');
            return {
              'success': true,
              'firstName': userData['firstName'] ?? '',
              'lastName': userData['lastName'] ?? '',
              'section': userData['section'] ?? '',
              'role': 'admin',
            };
          } else if (role.toLowerCase() == 'teacher' || role.toLowerCase() == 'child') {
            print('Пользователь $role. Проверяем, подтверждён ли он...');
            DocumentSnapshot confirmedDoc = await _firestore.collection('confirmed_${role.toLowerCase()}s').doc(user.uid).get();
            
            if (confirmedDoc.exists) {
              print('$role подтверждён');
              return {
                'success': true,
                'firstName': userData['firstName'] ?? '',
                'lastName': userData['lastName'] ?? '',
                'section': userData['section'] ?? '',
                'role': role,
              };
            } else {
              print('$role не подтверждён');
              await _firebaseAuth.signOut();
              return {'success': false, 'error': 'Пользователь не подтверждён'};
            }
          } else {
            print('Недействительная роль пользователя: $role');
            await _firebaseAuth.signOut();
            return {'success': false, 'error': 'Недействительная роль пользователя'};
          }
        } else {
          print('Документ пользователя не найден в коллекции пользователей');
          await _firebaseAuth.signOut();
          return {'success': false, 'error': 'Данные пользователя не найдены'};
        }
      } else {
        print('Объект пользователя равен null после аутентификации');
        return {'success': false, 'error': 'Пользователь не найден'};
      }
    } catch (e) {
      print('Ошибка во время входа: $e');
      return {'success': false, 'error': e.toString()};
    }
  }

  Future<void> handleGoogleSignIn(BuildContext context) async {
    try {
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      if (googleUser == null) return; // Пользователь отменил вход
      
      final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

      final AuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      final UserCredential userCredential = await _firebaseAuth.signInWithCredential(credential);
      final User? user = userCredential.user;

      if (user != null) {
        // Проверка роли пользователя в коллекции пользователей
        DocumentSnapshot userDoc = await _firestore.collection('users').doc(user.uid).get();

        if (userDoc.exists) {
          final userData = userDoc.data() as Map<String, dynamic>;
          final String role = userData['role'] ?? '';

          if (role == 'admin') {
            // Администратор может входить напрямую
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (context) => MainScreen(
                  firstName: userData['firstName'] ?? '',
                  lastName: userData['lastName'] ?? '',
                  section: userData['section'] ?? '',
                  role: role,
                ),
              ),
            );
          } else if (role == 'teacher' || role == 'child') {
            // Для учителей и детей проверьте, подтверждены ли они
            DocumentSnapshot confirmedDoc = await _firestore.collection('confirmed_${role}s').doc(user.uid).get();
            
            if (confirmedDoc.exists) {
              Navigator.pushReplacement(
                context,
                MaterialPageRoute(
                  builder: (context) => MainScreen(
                    firstName: userData['firstName'] ?? '',
                    lastName: userData['lastName'] ?? '',
                    section: userData['section'] ?? '',
                    role: role,
                  ),
                ),
              );
            } else {
              await _firebaseAuth.signOut();
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Пользователь не подтверждён')),
              );
            }
          } else {
            await _firebaseAuth.signOut();
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Недействительная роль пользователя')),
            );
          }
        } else {
          await _firebaseAuth.signOut();
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Данные пользователя не найдены')),
          );
        }
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Ошибка во время входа через Google: $e')),
      );
    }
  }

  Future<void> signOut() async {
    try {
      await _firebaseAuth.signOut();
      print('Пользователь вышел из системы');
    } catch (e) {
      print('Ошибка при выходе: $e');
      throw Exception('Не удалось выйти из системы: $e');
    }
  }

  User? getCurrentUser() {
    return _firebaseAuth.currentUser;
  }
}

Я хочу, чтобы система искала ожидающих учителей и отображала их в аутентификации учителя (строить элемент списка в меню). Администратор подтвердит одну из записей в ожидающих учителях, после подтверждения данные учителя будут перемещены в подтверждённых учителей.

В auth_controller система будет искать подтверждённых учителей, если роль пользователя учитель. Только тогда учитель сможет войти.

Но каждый раз, когда я подтверждаю учителя как администратор с помощью flutter run для отладки, я всегда теряю соединение с устройством.

Пожалуйста, пожалуйста, помогите мне.

Кроме того, в журнале ошибок нет ошибок.

Ответ или решение

Решение проблемы потери соединения с устройством при подтверждении учителей в приложении Flutter с использованием Firebase

Введение

Работа с Firebase и Flutter предоставляет мощные инструменты для реализации функционала аутентификации и управления данными. В вашем проекте есть цель: отобразить список ожидающих учителей и позволить администратору подтверждать их, перемещая записи в коллекцию подтвержденных учителей. Однако, вы столкнулись с проблемой: при подтверждении учителя, вы теряете соединение с устройством, и никакие ошибки не отображаются в логах. В этой статье мы рассмотрим причину проблемы и постараемся предложить возможные решения.

Возможные причины проблемы

  1. Долгая операция в потоке UI: Если операция подтверждения учителя занимает слишком много времени или блокирует основной поток (UI), это может привести к сбою приложения. Поскольку взаимодействие с Firebase, особенно при использовании транзакций, может занять значительное время, важно убедиться, что вы вызываете их правильно.

  2. Ошибка управления состоянием: Если ваше приложение не корректно обрабатывает состояние после выполнения асинхронных операций (например, подтверждение учителя), может произойти сбой в виджетах, используемых в StreamBuilder.

  3. Необработанные исключения: Даже если ошибок не отображается в логах, это не означает, что они отсутствуют. Обязательно проверьте правильность обработки исключений в ваших методах.

  4. Проблемы с сетью: Итогом может быть потеря соединения с Firebase не только из-за кода, но и из-за проблем на стороне сети. Программа может случайно разорвать соединение с интернетом при работе в среде отладки.

Рекомендации по устранению проблемы

  1. Асинхронная инициализация:
    Убедитесь, что инициализация Firebase производится на начальном этапе работы приложения, возможно, в методе main(), перед вызовом основного виджета. Это может защитить вас от проблем с инициализацией:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      runApp(MyApp());
    }
  2. Использование FutureBuilder для асинхронных операций:
    Вместо вызова _confirmTeacher напрямую, рассмотрите возможность использования FutureBuilder, чтобы управлять состоянием и отобразить индикатор загрузки. Это позволит избежать блокировки UI:

    @override
    Widget build(BuildContext context) {
      return FutureBuilder<void>(
        future: _confirmTeacher(context, teachers[index].id, teacherData),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Ошибка: ${snapshot.error}');
          } else {
            // Отобразить информацию о подтвержденных учителях или другое содержимое
          }
        },
      );
    }
  3. Информационные сообщения для пользователя:
    Добавьте больше сообщений о статусе по завершении операций подтверждения. Например, используйте SnackBar для уведомления пользователя о том, что операция завершилась успешно или с ошибкой, чтобы улучшить пользовательский опыт.

  4. Обработка ошибок:
    Убедитесь, что у вас есть обработка ошибок везде на случай возникновения непредвиденных обстоятельств. Не оставляйте никаких «пустых» случаев, так как это может затруднить диагностику проблем в будущем.

  5. Анализ сети:
    Проверьте стабильность вашего интернет-соединения на устройстве, используемом для отладки вашего приложения. Попробуйте использовать эмуляторы, которые симулируют разные сети, и проверьте, сохраняется ли проблема.

Заключение

Проблема потери соединения с устройством при подтверждении учителей в вашем приложении Flutter может быть вызвана несколькими факторами, включая блокировки UI, ошибки управления состоянием и сетевые неполадки. Правильная обработка асинхронных операций и пользователей — ключевая часть решения. Применение предложенных рекомендаций позволит вам улучшить стабильность приложения и обеспечить лучший пользовательский опыт.

Оцените материал
Добавить комментарий

Капча загружается...