আমি কীভাবে ফ্লটারে ফায়ারস্টোর দুটি সংগ্রহ থেকে ডেটাতে যোগদান করব?


9

ফায়ার স্টোর ব্যবহার করে আমার ফ্লাটারে একটি চ্যাট অ্যাপ রয়েছে এবং আমার দুটি প্রধান সংগ্রহ রয়েছে:

  • chatsযা স্বয়ং-ID- এর উপর অস্থির, আর রয়েছে message, timestampএবং uidক্ষেত্র।
  • users, যা কীযুক্ত uidএবং একটি nameক্ষেত্র রয়েছে

আমার অ্যাপ্লিকেশনটিতে আমি messagesএই উইজেটের সাথে বার্তাগুলির একটি তালিকা ( সংগ্রহ থেকে ) দেখাব :

class ChatList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var messagesSnapshot = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
    var streamBuilder = StreamBuilder<QuerySnapshot>(
          stream: messagesSnapshot,
          builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapshot) {
            if (querySnapshot.hasError)
              return new Text('Error: ${querySnapshot.error}');
            switch (querySnapshot.connectionState) {
              case ConnectionState.waiting: return new Text("Loading...");
              default:
                return new ListView(
                  children: querySnapshot.data.documents.map((DocumentSnapshot doc) {
                    return new ListTile(
                      title: new Text(doc['message']),
                      subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(doc['timestamp']).toString()),
                    );
                  }).toList()
                );
            }
          }
        );
        return streamBuilder;
  }
}

তবে এখন আমি usersপ্রতিটি বার্তার জন্য ব্যবহারকারীর নাম ( সংগ্রহ থেকে ) দেখাতে চাই ।

আমি সাধারণত কল করি যে ক্লায়েন্ট-সাইড যোগ দেয়, যদিও আমি নিশ্চিত নই যে ফ্লুটারের নির্দিষ্ট নাম রয়েছে কিনা।

আমি এটি করার একটি উপায় খুঁজে পেয়েছি (যা আমি নীচে পোস্ট করেছি) তবে বিস্মিত হোন যে এলোমেলোভাবে এই ধরণের অপারেশন করার জন্য আরও কোনও / আরও ভাল / আরও মূর্তিমান পদ্ধতি রয়েছে কিনা।

সুতরাং: উপরের কাঠামোর প্রতিটি বার্তার জন্য ব্যবহারকারীর নাম সন্ধানের জন্য ফ্লটারে কী অহংকার রয়েছে?


আমি একমাত্র সমাধান আমি rxdart অনেকটা গবেষণা আছে মনে
Cenk YAGMUR

উত্তর:


3

আমি আর একটি সংস্করণ কাজ করেছি যা দুটি নেস্টেড বিল্ডারের সাথে আমার উত্তরের চেয়ে কিছুটা ভাল বলে মনে হচ্ছে ।

এখানে আমি কোনও কাস্টম পদ্ধতিতে ডেটা লোডিংয়ের ক্ষেত্রে বিচ্ছিন্ন হয়েছি, Messageকোনও বার্তা Documentএবং alচ্ছিক সম্পর্কিত ব্যবহারকারীর কাছ থেকে তথ্য ধরে রাখতে একটি উত্সর্গীকৃত ক্লাস ব্যবহার করে Document

class Message {
  final message;
  final timestamp;
  final uid;
  final user;
  const Message(this.message, this.timestamp, this.uid, this.user);
}
class ChatList extends StatelessWidget {
  Stream<List<Message>> getData() async* {
    var messagesStream = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
    var messages = List<Message>();
    await for (var messagesSnapshot in messagesStream) {
      for (var messageDoc in messagesSnapshot.documents) {
        var message;
        if (messageDoc["uid"] != null) {
          var userSnapshot = await Firestore.instance.collection("users").document(messageDoc["uid"]).get();
          message = Message(messageDoc["message"], messageDoc["timestamp"], messageDoc["uid"], userSnapshot["name"]);
        }
        else {
          message = Message(messageDoc["message"], messageDoc["timestamp"], "", "");
        }
        messages.add(message);
      }
      yield messages;
    }
  }
  @override
  Widget build(BuildContext context) {
    var streamBuilder = StreamBuilder<List<Message>>(
          stream: getData(),
          builder: (BuildContext context, AsyncSnapshot<List<Message>> messagesSnapshot) {
            if (messagesSnapshot.hasError)
              return new Text('Error: ${messagesSnapshot.error}');
            switch (messagesSnapshot.connectionState) {
              case ConnectionState.waiting: return new Text("Loading...");
              default:
                return new ListView(
                  children: messagesSnapshot.data.map((Message msg) {
                    return new ListTile(
                      title: new Text(msg.message),
                      subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(msg.timestamp).toString()
                                         +"\n"+(msg.user ?? msg.uid)),
                    );
                  }).toList()
                );
            }
          }
        );
        return streamBuilder;
  }
}

নেস্টেড বিল্ডারদের সাথে সমাধানের তুলনায় এই কোডটি আরও পঠনযোগ্য, বেশিরভাগ কারণে ডেটা হ্যান্ডলিং এবং ইউআই বিল্ডার আরও ভালভাবে পৃথক হয়। এটি কেবল বার্তা পোস্টকারী ব্যবহারকারীদের জন্য ব্যবহারকারীর নথিগুলি লোড করে। দুর্ভাগ্যক্রমে, যদি ব্যবহারকারী একাধিক বার্তা পোস্ট করে থাকে তবে এটি প্রতিটি বার্তার জন্য দস্তাবেজটি লোড করবে। আমি একটি ক্যাশে যুক্ত করতে পারি, তবে মনে করি এই কোডটি এটি সম্পাদন করে এর জন্য ইতিমধ্যে কিছুটা দীর্ঘ।


1
আপনি যদি উত্তর হিসাবে "মেসেজের অভ্যন্তরে ব্যবহারকারীর তথ্য সংরক্ষণ" না করেন তবে আমার মনে হয় এটি আপনার পক্ষে সবচেয়ে ভাল। আপনি যদি মেসেজের ভিতরে ব্যবহারকারীর তথ্য সঞ্চয় করেন তবে এই স্পষ্টতই অসুবিধা আছে যে ব্যবহারকারীর তথ্য ব্যবহারকারীর সংগ্রহে পরিবর্তিত হতে পারে তবে ম্যাসেজের ভিতরে নয়। একটি নির্ধারিত ফায়ারবেস ফাংশন ব্যবহার করে আপনি এটি সমাধানও করতে পারেন। সময়ে সময়ে, আপনি বার্তাগুলি সংগ্রহের মাধ্যমে যেতে এবং ব্যবহারকারীদের সংগ্রহের সর্বশেষ ডেটা অনুসারে ব্যবহারকারীর তথ্য আপডেট করতে পারেন।
উগুরকান ইল্ডিরিম

ব্যক্তিগতভাবে আমি স্ট্রিমগুলির সংমিশ্রণের তুলনায় সত্যিকারের প্রয়োজন না হলে এর চেয়ে সহজ সমাধানটিকে আমি পছন্দ করি। আরও ভাল, আমরা এই ডেটা লোডিং পদ্ধতিটি কোনও পরিষেবা শ্রেণীর মতো কিছুতে বা বিএলওসি প্যাটার্নটি অনুসরণ করতে পারি। যেমন আপনি ইতিমধ্যে উল্লেখ করেছেন, আমরা ব্যবহারকারীর তথ্য একটিতে সংরক্ষণ করতে পারি Map<String, UserModel>এবং কেবল একবার ব্যবহারকারীর নথিটি লোড করতে পারি।
জোশুয়া চান

জোশুয়াকে রাজি করলেন। আমি কীভাবে এটি একটি বিএলওসি প্যাটার্নে দেখতে চাই তার একটি লিখন আপ দেখতে চাই।
ফ্রাঙ্ক ভ্যান পাফেলেন

3

যদি আমি এটি সঠিকভাবে পড়ছি, সমস্যাটি বিমোহিত হয়: আপনি কীভাবে এমন স্ট্রিমটির এমন একটি স্ট্রিমকে রূপান্তর করবেন যা প্রবাহে ডেটা পরিবর্তন করার জন্য একটি অ্যাসিনক্রোনাস কল করা দরকার?

সমস্যার প্রসঙ্গে ডেটা প্রবাহটি বার্তাগুলির একটি তালিকা, এবং অ্যাসিঙ্ক কলটি ব্যবহারকারী ডেটা আনতে এবং প্রবাহে এই ডেটা সহ বার্তাগুলি আপডেট করা।

asyncMap()ফাংশনটি ব্যবহার করে ডার্ট স্ট্রিম অবজেক্টে সরাসরি এটি করা সম্ভব। এখানে কিছু খাঁটি ডার্ট কোড রয়েছে যা এটি কীভাবে করবেন তা দেখায়:

import 'dart:async';
import 'dart:math' show Random;

final random = Random();

const messageList = [
  {
    'message': 'Message 1',
    'timestamp': 1,
    'uid': 1,
  },
  {
    'message': 'Message 2',
    'timestamp': 2,
    'uid': 2,
  },
  {
    'message': 'Message 3',
    'timestamp': 3,
    'uid': 2,
  },
];

const userList = {
  1: 'User 1',
  2: 'User 2',
  3: 'User 3',
};

class Message {
  final String message;
  final int timestamp;
  final int uid;
  final String user;
  const Message(this.message, this.timestamp, this.uid, this.user);

  @override
  String toString() => '$user => $message';
}

// Mimic a stream of a list of messages
Stream<List<Map<String, dynamic>>> getServerMessagesMock() async* {
  yield messageList;
  while (true) {
    await Future.delayed(Duration(seconds: random.nextInt(3) + 1));
    yield messageList;
  }
}

// Mimic asynchronously fetching a user
Future<String> userMock(int uid) => userList.containsKey(uid)
    ? Future.delayed(
        Duration(milliseconds: 100 + random.nextInt(100)),
        () => userList[uid],
      )
    : Future.value(null);

// Transform the contents of a stream asynchronously
Stream<List<Message>> getMessagesStream() => getServerMessagesMock()
    .asyncMap<List<Message>>((messageList) => Future.wait(
          messageList.map<Future<Message>>(
            (m) async => Message(
              m['message'],
              m['timestamp'],
              m['uid'],
              await userMock(m['uid']),
            ),
          ),
        ));

void main() async {
  print('Streams with async transforms test');
  await for (var messages in getMessagesStream()) {
    messages.forEach(print);
  }
}

কোডের বেশিরভাগটি ফায়ারবেস থেকে আসা বার্তাগুলির মানচিত্রের স্ট্রিম এবং ডেটা এবং ইউজার ডেটা আনার জন্য একটি অ্যাসিঙ্ক ফাংশন থেকে আগত ডেটা নকল করে। এখানে গুরুত্বপূর্ণ কাজটি হচ্ছে getMessagesStream()

কোডটি প্রবাহে আসা বার্তাগুলির একটি তালিকা দ্বারা কিছুটা জটিল। ব্যবহারকারীদের ডেটা আনতে কলকে সিঙ্ক্রোনজলি আটকাতে, কোডটি একটি Future.wait()সংগ্রহ List<Future<Message>>করতে এবং List<Message>সমস্ত ফিউচার সমাপ্ত হলে একটি তৈরি করতে একটি ব্যবহার করে ।

ঝাঁকুনির প্রসঙ্গে আপনি বার্তা অবজেক্টগুলি প্রদর্শন করতে স্ট্রিম ব্যবহার করতে পারেন getMessagesStream()aFutureBuilder


3

আপনি এটির মতো আরএক্সডার্ট দিয়ে এটি করতে পারেন .. https://pub.dev/packages/rxdart

import 'package:rxdart/rxdart.dart';

class Messages {
  final String messages;
  final DateTime timestamp;
  final String uid;
  final DocumentReference reference;

  Messages.fromMap(Map<String, dynamic> map, {this.reference})
      : messages = map['messages'],
        timestamp = (map['timestamp'] as Timestamp)?.toDate(),
        uid = map['uid'];

  Messages.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  @override
  String toString() {
    return 'Messages{messages: $messages, timestamp: $timestamp, uid: $uid, reference: $reference}';
  }
}

class Users {
  final String name;
  final DocumentReference reference;

  Users.fromMap(Map<String, dynamic> map, {this.reference})
      : name = map['name'];

  Users.fromSnapshot(DocumentSnapshot snapshot)
      : this.fromMap(snapshot.data, reference: snapshot.reference);

  @override
  String toString() {
    return 'Users{name: $name, reference: $reference}';
  }
}

class CombineStream {
  final Messages messages;
  final Users users;

  CombineStream(this.messages, this.users);
}

Stream<List<CombineStream>> _combineStream;

@override
  void initState() {
    super.initState();
    _combineStream = Observable(Firestore.instance
        .collection('chat')
        .orderBy("timestamp", descending: true)
        .snapshots())
        .map((convert) {
      return convert.documents.map((f) {

        Stream<Messages> messages = Observable.just(f)
            .map<Messages>((document) => Messages.fromSnapshot(document));

        Stream<Users> user = Firestore.instance
            .collection("users")
            .document(f.data['uid'])
            .snapshots()
            .map<Users>((document) => Users.fromSnapshot(document));

        return Observable.combineLatest2(
            messages, user, (messages, user) => CombineStream(messages, user));
      });
    }).switchMap((observables) {
      return observables.length > 0
          ? Observable.combineLatestList(observables)
          : Observable.just([]);
    })
}

rxdart 0.23.x এর জন্য

@override
      void initState() {
        super.initState();
        _combineStream = Firestore.instance
            .collection('chat')
            .orderBy("timestamp", descending: true)
            .snapshots()
            .map((convert) {
          return convert.documents.map((f) {

            Stream<Messages> messages = Stream.value(f)
                .map<Messages>((document) => Messages.fromSnapshot(document));

            Stream<Users> user = Firestore.instance
                .collection("users")
                .document(f.data['uid'])
                .snapshots()
                .map<Users>((document) => Users.fromSnapshot(document));

            return Rx.combineLatest2(
                messages, user, (messages, user) => CombineStream(messages, user));
          });
        }).switchMap((observables) {
          return observables.length > 0
              ? Rx.combineLatestList(observables)
              : Stream.value([]);
        })
    }

খুব ঠান্ডা! প্রয়োজন নেই এমন কোনও উপায় আছে f.reference.snapshots()কেননা যেটি মূলত স্ন্যাপশটটি পুনরায় লোড করছে এবং আমি ফায়ারস্টোর ক্লায়েন্টের উপর নির্ভর করতে পছন্দ করি না যেগুলি তাদের প্রতিলিপি দেওয়ার জন্য যথেষ্ট (যদিও আমি প্রায় নিশ্চিত যে এটি ডিপোপ করে))
ফ্র্যাঙ্ক ভ্যান পাফেলেন

এটি পেয়েছি। পরিবর্তে Stream<Messages> messages = f.reference.snapshots()..., আপনি করতে পারেন Stream<Messages> messages = Observable.just(f)...। এই উত্তর সম্পর্কে আমি যা পছন্দ করি তা হ'ল এটি ব্যবহারকারীর নথিগুলি পর্যবেক্ষণ করে, তাই যদি কোনও ব্যবহারকারীর নাম ডাটাবেসে আপডেট হয় তবে আউটপুটটি ঠিক এখনই প্রতিফলিত হয়।
ফ্রাঙ্ক ভ্যান পাফেলেন

হ্যাঁ এত ভাল কাজ করে যাচ্ছি আমার কোড আপডেট করে
কেনেক ইয়াগমুর

1

আদর্শভাবে আপনি কোনও ব্যবসায়ের যুক্তি যেমন কোনও পৃথক পরিষেবাতে ডেটা লোড করা বা ব্লক প্যাটার্ন অনুসরণ করে, বাদ দিতে চান:

class ChatBloc {
  final Firestore firestore = Firestore.instance;
  final Map<String, String> userMap = HashMap<String, String>();

  Stream<List<Message>> get messages async* {
    final messagesStream = Firestore.instance.collection('chat').orderBy('timestamp', descending: true).snapshots();
    var messages = List<Message>();
    await for (var messagesSnapshot in messagesStream) {
      for (var messageDoc in messagesSnapshot.documents) {
        final userUid = messageDoc['uid'];
        var message;

        if (userUid != null) {
          // get user data if not in map
          if (userMap.containsKey(userUid)) {
            message = Message(messageDoc['message'], messageDoc['timestamp'], userUid, userMap[userUid]);
          } else {
            final userSnapshot = await Firestore.instance.collection('users').document(userUid).get();
            message = Message(messageDoc['message'], messageDoc['timestamp'], userUid, userSnapshot['name']);
            // add entry to map
            userMap[userUid] = userSnapshot['name'];
          }
        } else {
          message =
              Message(messageDoc['message'], messageDoc['timestamp'], '', '');
        }
        messages.add(message);
      }
      yield messages;
    }
  }
}

তারপরে আপনি কেবল নিজের উপাদানটিতে ব্লকটি ব্যবহার করতে এবং chatBloc.messagesস্ট্রিমটি শুনতে পারবেন ।

class ChatList extends StatelessWidget {
  final ChatBloc chatBloc = ChatBloc();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Message>>(
        stream: chatBloc.messages,
        builder: (BuildContext context, AsyncSnapshot<List<Message>> messagesSnapshot) {
          if (messagesSnapshot.hasError)
            return new Text('Error: ${messagesSnapshot.error}');
          switch (messagesSnapshot.connectionState) {
            case ConnectionState.waiting:
              return new Text('Loading...');
            default:
              return new ListView(children: messagesSnapshot.data.map((Message msg) {
                return new ListTile(
                  title: new Text(msg.message),
                  subtitle: new Text('${msg.timestamp}\n${(msg.user ?? msg.uid)}'),
                );
              }).toList());
          }
        });
  }
}

1

আমাকে আরএক্সডার্ট সমাধানের আমার সংস্করণটি রাখার অনুমতি দিন। আমি প্রতিটি বার্তা উইজেট তৈরি করতে combineLatest2একটি ব্যবহার করি ListView.builder। প্রতিটি বার্তা উইজেট নির্মাণের সময় আমি সংশ্লিষ্টটির সাথে ব্যবহারকারীর নাম অনুসন্ধান করি uid

এই স্নিপেটে আমি ব্যবহারকারীর নামের জন্য একটি রৈখিক চেহারা ব্যবহার করি তবে এটি একটি uid -> user nameমানচিত্র তৈরি করে উন্নত করা যায়

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/widgets.dart';
import 'package:rxdart/rxdart.dart';

class MessageWidget extends StatelessWidget {
  // final chatStream = Firestore.instance.collection('chat').snapshots();
  // final userStream = Firestore.instance.collection('users').snapshots();
  Stream<QuerySnapshot> chatStream;
  Stream<QuerySnapshot> userStream;

  MessageWidget(this.chatStream, this.userStream);

  @override
  Widget build(BuildContext context) {
    Observable<List<QuerySnapshot>> combinedStream = Observable.combineLatest2(
        chatStream, userStream, (messages, users) => [messages, users]);

    return StreamBuilder(
        stream: combinedStream,
        builder: (_, AsyncSnapshot<List<QuerySnapshot>> snapshots) {
          if (snapshots.hasData) {
            List<DocumentSnapshot> chats = snapshots.data[0].documents;

            // It would be more efficient to convert this list of user documents
            // to a map keyed on the uid which will allow quicker user lookup.
            List<DocumentSnapshot> users = snapshots.data[1].documents;

            return ListView.builder(itemBuilder: (_, index) {
              return Center(
                child: Column(
                  children: <Widget>[
                    Text(chats[index]['message']),
                    Text(getUserName(users, chats[index]['uid'])),
                  ],
                ),
              );
            });
          } else {
            return Text('loading...');
          }
        });
  }

  // This does a linear search through the list of users. However a map
  // could be used to make the finding of the user's name more efficient.
  String getUserName(List<DocumentSnapshot> users, String uid) {
    for (final user in users) {
      if (user['uid'] == uid) {
        return user['name'];
      }
    }
    return 'unknown';
  }
}

আর্থারকে দেখে খুব শীতল। এটি নেস্টেড বিল্ডারদের সাথে আমার প্রাথমিক উত্তরের অনেক ক্লিনার সংস্করণের মতো । অবশ্যই পড়ার আরও সহজ সমাধানগুলির একটি।
ফ্র্যাঙ্ক ভ্যান পাফেলেন 10'19

0

প্রথম সমাধানটি আমি কাজ করেছিলাম হ'ল দুটি StreamBuilderঘটনা বাসা বাঁধে , প্রতিটি সংগ্রহ / প্রশ্নের জন্য একটি।

class ChatList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var messagesSnapshot = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
    var usersSnapshot = Firestore.instance.collection("users").snapshots();
    var streamBuilder = StreamBuilder<QuerySnapshot>(
      stream: messagesSnapshot,
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> messagesSnapshot) {
        return StreamBuilder(
          stream: usersSnapshot,
          builder: (context, usersSnapshot) {
            if (messagesSnapshot.hasError || usersSnapshot.hasError || !usersSnapshot.hasData)
              return new Text('Error: ${messagesSnapshot.error}, ${usersSnapshot.error}');
            switch (messagesSnapshot.connectionState) {
              case ConnectionState.waiting: return new Text("Loading...");
              default:
                return new ListView(
                  children: messagesSnapshot.data.documents.map((DocumentSnapshot doc) {
                    var user = "";
                    if (doc['uid'] != null && usersSnapshot.data != null) {
                      user = doc['uid'];
                      print('Looking for user $user');
                      user = usersSnapshot.data.documents.firstWhere((userDoc) => userDoc.documentID == user).data["name"];
                    }
                    return new ListTile(
                      title: new Text(doc['message']),
                      subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(doc['timestamp']).toString()
                                          +"\n"+user),
                    );
                  }).toList()
                );
            }
        });
      }
    );
    return streamBuilder;
  }
}

আমার প্রশ্নের হিসাবে বলা হয়েছে, আমি জানি এই সমাধানটি দুর্দান্ত নয়, তবে কমপক্ষে এটি কার্যকর হয়।

আমি এটি দিয়ে কিছু সমস্যা দেখতে পাচ্ছি:

  • এটি কেবলমাত্র বার্তা পোস্টকারী ব্যবহারকারীদের পরিবর্তে সমস্ত ব্যবহারকারীকে বোঝায়। ছোট ডেটা সেটগুলিতে যা কোনও সমস্যা হবে না তবে আমি আরও বার্তা / ব্যবহারকারী পেয়েছি (এবং সেগুলির একটি উপসেট দেখানোর জন্য একটি কোয়েরি ব্যবহার করব) আমি আরও বেশি বেশি ব্যবহারকারী লোড করব যাঁরা কোনও বার্তা পোস্ট করেননি।
  • কোডটি দুটি বিল্ডারের বাসা বাঁধার সাথে সত্যই খুব পঠনযোগ্য নয়। আমি সন্দেহ করি এটি মূর্খ নাটক

আপনি যদি আরও ভাল সমাধান জানেন তবে দয়া করে উত্তর হিসাবে পোস্ট করুন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.