

[teratail](https://teratail.com/questions/241219) でも同じ質問を投稿しています。

Androidで、Firebase(FirebaseStorage,Realtimedatabase)を使用して画像やテキストなどを表示するアプリを開発しています。  
テキストデータはRealtimedatabase、画像はdownloadURLをRealtimedatabaseに保持して、そこから画像の実体をFirebaseStorageから取ってくるようにしております。

しかし、実装してみると画像が表示されなくなったりテキストを表示されるべきところに画像が表示されるなど、意図しない挙動が起きています。  
原因として推測されるのは、以下のように画像の取得で用いられるFirebaseStorageとそれ以外の取得に用いられるRealtimedatabaseそれぞれが非同期処理で動いているためかと思われます。

例えば、realtimeDatabase側の「for(DataSnapshot
ds・・・」の4ループ目が画像1だったとして、その画像データの取得(FireStorage側)が完了した時、Realtimedatabaseが7ループ目まで進んでいたとしたら、本来4ループ目のmessageDataBeanに画像データを入れるはずが7ループ目のmessageDataBeanを更新してしまうということです。

これを防ぐには例えば4ループ目で取得し始めた画像を取得完了後に、4ループ目のListの要素（messageDataBean）の画像フィールドにセット、8ループ目で取得しようとした画像を8ループ目のListの要素にセットというように処理を実装する必要があるのですが、その方法を教えていただけないでしょうか。

また、以下のページを参考にしてループごとにロックして画像取得の順番を保持しようとしたのですがうまく行きませんでした。

[Javaの排他制御(ロック)に関係するクラスまとめ -
Qiita](https://qiita.com/takezoux2/items/7560c5151b58f1bd2c6f)

の「java.concurrent.locks.Lock, Condition」というページ

[code]

    DatabaseReferense ref = ;
    Bitmap  imgBitmap；
    ArrayList<MessageDataBean> messageDataBean = new ArrayList<MessageDataBean>();
    //DBを読み出し(画像以外はRealtimedatabase)
    ref.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            for(DataSnapshot ds : dataSnapshot.getChildren()){
                String txtData1;
                String txtData2;
                String msgType = ds.child("type").getValue();
    
                if(msgType.equals("txt")){//テキストの場合
                    txtData1 = ds.child("txt1").getValue();
                    txtData2 = ds.child("txt2").getValue();
    
                }else if(msgType.equals("img")){//画像の場合
                    //画像のダウンロードURL
                    String downloadURL = String.valueOf(ds.child("img_dl_url").getValue());
                    StorageReference images = FirebaseStorage.getInstance().getReferenceFromUrl(downloadURL);
                    //ダウンロードの最大のサイズを指定する
                    final long ONE_MEGABYTE = 1024 * 1024;
    
                    images.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
                        @Override
                        public void onSuccess(byte[] bytes) {
                            // 画像のダウンロード成功
                            imgBitmap = OtherUtils.createBitmap(bytes,1024,1024);
                        }
                    }).addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception exception) {
                            // 画像のダウンロード失敗
                        }
                    });
                }
    
                MessageDataBean messageDataBean = new MessageDataBean(
                    txtData1,
                    txtData2,
                    imgBitmap,
                );
    
                messageDataBeanList.add(messageDataBean);
                show(messageDataBeanList);//表示内容更新処理  
            }
        }
        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {
        }
    });
    
[/code]


CC BY-SA 4.0
2020-02-13T15:16:04.370
63027
2020-03-01T14:18:21.120
2020-02-13T16:59:00.613
3060
18805
question
java android firebase android-asynctask
Androidで、Firebase(FirebaseStorage,Realtimedatabase)を使用して画像を表示したい


こんにちは、はじめまして。コードを拝見させていただきました。  
気になった点を挙げさせていただきます。

  * forループの中で画像を読み込んでいる箇所にまず問題があるかとおもいます。onSuccessが直後のmessageDataBeanへの代入までに呼ばれる保証はありません(時間がかかる処理のため別プロセスで読み込んでそれが完了するとonSuccessが呼ばれます）。ですのでArrayListに代入されたbitmapがアプリにどうしても必要なのであれば、onSuccessの中で代入と、リストへの要素追加を行ったほうがいいと思います。ただしこの流れではuiへの反映は、同期させる必要があり難しいです。他の手法を検討されることをおすすめします
  * この時点では画像を読み込まずにURL情報からビューに直接`Glide`などのライブラリで読み込むようにしたほうが良いと思います。  
<https://qiita.com/hotchemi/items/375d63261f2eed2b18e1>

  * 頂いたコードから画面への表示方法が推察できませんでした。Androidでは利用するビューにより、実装方法はある程度きまった形を追うことが多いです。codelab(公式)のFriendlyChatはリアルタイムデータベースの処理とStorageからの画像の表示を取り扱っていますので参考になるかもしれません  
<https://codelabs.developers.google.com/codelabs/firebase-android/#1>  
<https://github.com/firebase/friendlychat-android>

  * このケースでは排他制御などは気にしなくても大丈夫です

**追記**  
~~あまり使ったことはないのですが以下のような手段でListenerを経由せずに、画像のダウンロードの終了を待機することができるようです。getBytesの戻り値のTaskを取得して、Tasks.waitで同期しforループで読み込みが完結するようにするのは、表示周りのコードがいじれないのであれば、ありかもしれません。~~  
~~<https://developers.google.com/android/guides/tasks>~~

Firebase自体が非同期の実装を意図して作られています。Tasksで呼び出しの順序を固定したり、RestAPIで直接操作することで同期処理を実現することは可能だとは思いますが、極力避けてGoogleの提供する実装をなぞることをおすすめします。

<https://developers-jp.googleblog.com/2016/09/become-a-firebase-taskmaster-
part-1.html>  
<https://stackoverflow.com/questions/31700830/is-it-possible-to-synchronously-
load-data-from-firebase>


CC BY-SA 4.0
2020-02-14T02:49:38.080
63033
2020-02-26T08:28:08.213
2020-02-26T08:28:08.213
37777
37777
63027
answer