简体   繁体   English

无法从 Firebase 存储中获取实际的字符串下载 url 并且仅返回“未来”的实例<string> ' 甚至使用 async/await</string>

[英]Can't get actual String download url from Firebase Storage and only returns Instance of 'Future<String>' even using async/await

I am trying to get user avatar from firebase storage, however, my current code only returns Instance of 'Future<String>' even I am using async/await as below.我正在尝试从 firebase 存储中获取用户头像,但是,我当前的代码仅返回Instance of 'Future<String>'即使我使用如下所示的 async/await。 How is it possible to get actual download URL as String, rather Instance of Future so I can access the data from CachedNewtworkImage?如何获得实际下载 URL 作为字符串,而不是Future 的实例,以便我可以从 CachedNewtworkImage 访问数据?

this is the function that calls getAvatarDownloadUrl with current passed firebase user instance.这是 function 调用 getAvatarDownloadUrl 并当前通过 firebase 用户实例。 myViewModel

  FutureOr<String> getAvatarUrl(User user) async {
    var snapshot = await _ref
        .read(firebaseStoreRepositoryProvider)
        .getAvatarDownloadUrl(user.code);
    if (snapshot != null) {
      print("avatar url: $snapshot");
    }
    return snapshot;
  }

getAvatarURL is basically first calling firebase firestore reference then try to access to the downloadURL, if there is no user data, simply returns null. getAvatarURL 基本上是先调用firebase firestore 引用然后尝试访问downloadURL,如果没有用户数据,则简单返回null。

  Future<String> getAvatarDownloadUrl(String code) async {
    Reference _ref =
        storage.ref().child("users").child(code).child("asset.jpeg");
    try {
      String url = await _ref.getDownloadURL();
      return url;
    } on FirebaseException catch (e) {
      print(e.code);
      return null;
    }
}

I am calling these function from HookWidget called ShowAvatar.我从名为 ShowAvatar 的 HookWidget 中调用这些 function。 To show current user avatar, I use useProvider and useFuture to actually use the data from the database, and this code works with no problem.为了显示当前用户头像,我使用useProvideruseFuture来实际使用数据库中的数据,这段代码没有问题。 However, once I want to get downloardURL from list of users (inside of ListView using index),但是,一旦我想从用户列表中获取 downloardURL(在 ListView 内部使用索引),

class ShowAvatar extends HookWidget {
// some constructors...

 @override
  Widget build(BuildContext context) {
    // get firebase user instance
    final user = useProvider(accountProvider.state).user;
    // get user avatar data as Future<String>
    final userLogo = useProvider(firebaseStoreRepositoryProvider)
        .getAvatarDownloadUrl(user.code);
    // get actual user data as String
    final snapshot = useFuture(userLogo);
    // to access above functions inside of ListView
    final viewModel = useProvider(myViewModel);

    return SingleChildScrollView(
      physics: AlwaysScrollableScrollPhysics(),
      child: Container(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              height: 100,
              width: 100,
              child: Avatar(
                avatarUrl: snapshot.data, // **this avatar works!!!** so useProvider & useFuture is working
              ),
            ),
            SizedBox(height: 32),
            ListView.builder(
              shrinkWrap: true,
              physics: NeverScrollableScrollPhysics(),
              itemBuilder: (context, index) {
                return Center(
                  child: Column(
                    children: [
                      SizedBox(
                        height: 100,
                        width: 100,
                         child: Avatar(
                           avatarUrl: viewModel
                               .getAvatarUrl(goldWinners[index].user)
                               .toString(), // ** this avatar data is not String but Instance of Future<String>
                         ),
                      ),
                      
                      ),
                    ],
                  ),
                );
              },
              itemCount: goldWinners.length,
            ),

Avatar() is simple statelesswidget which returns ClipRRect if avatarURL is not existed (null), it returns simplace placeholder otherwise returns user avatar that we just get from firebase storage. Avatar() 是一个简单的无状态小部件,如果 avatarURL 不存在(null)则返回 ClipRRect,它返回 simplace 占位符,否则返回我们刚刚从 firebase 存储中获取的用户头像。 However, since users from ListView's avatarUrl is Instance of Future<String> I can't correctly show user avatar.但是,由于来自 ListView 的 avatarUrl 的用户是Instance of Future<String>因此我无法正确显示用户头像。

I tried to convert the instance to String multiple times by adding .toString() , but it didn't work.我尝试通过添加.toString()多次将实例转换为 String ,但没有成功。

class Avatar extends StatelessWidget {
  final String avatarUrl;
  final double radius;
  final BoxFit fit;

  Avatar({Key key, this.avatarUrl, this.radius = 16, this.fit})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('this is avatar url :   ' + avatarUrl.toString());
    return avatarUrl == null
        ? ClipRRect(
            borderRadius: BorderRadius.circular(radius),
            child: Image.asset(
              "assets/images/avatar_placeholder.png", 
              fit: fit,
            ),
          )
        : ClipRRect(
            borderRadius: BorderRadius.circular(radius),
            child: CachedNetworkImage(
              imageUrl: avatarUrl.toString(),
              placeholder: (_, url) => Skeleton(radius: radius),
              errorWidget: (_, url, error) => Icon(Icons.error),
              fit: fit,
            ));
  }
}

Since the download URL is asynchronously determined, it is returned as Future<String> from your getAvatarUrl method.由于下载 URL 是异步确定的,因此它会从您的getAvatarUrl方法中以Future<String>的形式返回。 To display a value from a Future , use a FutureBuilder widget like this:要显示来自Future的值,请使用FutureBuilder小部件,如下所示:

child: FutureBuilder<String>(
  future: viewModel.getAvatarUrl(goldWinners[index].user),
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    return snapshot.hashData
      ? Avatar(avatarUrl: snapshot.data)
      : Text("Loading URL...")
  }
)

Frank actually you gave an good start but there are some improvements we can do to handle the errors properly,弗兰克实际上你开了个好头,但是我们可以做一些改进来正确处理错误,

new FutureBuilder(
            future: //future you need to pass,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return new ListView.builder(
                    itemCount: snapshot.data.docs.length,
                    itemBuilder: (context, i) {
                      DocumentSnapshot ds = snapshot.data.docs[i];
                      return //the data you need to return using /*ds.data()['field value of doc']*/
                    });
              } else if (snapshot.hasError) {

                // Handle the error and stop rendering
                GToast(
                        message:
                            'Error while fetching data : ${snapshot.error}',
                        type: true)
                    .toast();
                return new Center(
                  child: new CircularProgressIndicator(),
                );
              } else {
                // Wait for the data to fecth
                return new Center(
                  child: new CircularProgressIndicator(),
                );
              }
            }),

Now if you are using a text widget as a return statement in case of errors it will be rendered forever.现在,如果您使用文本小部件作为返回语句以防出现错误,它将永远呈现。 Incase of Progress Indicators, you will exactly know if it is an error it will show the progress indicator and then stop the widget rendering.如果是进度指示器,您将确切知道它是否是错误,它将显示进度指示器,然后停止小部件渲染。

else if (snapshot.hasError) {
}
else {

}

above statement renders until, if there is an error or the builder finished fetching the results and ready to show the result widget.如果出现错误或构建器完成获取结果并准备显示结果小部件,则上述语句呈现直到。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM