0

I'm trying get images from Firebase Storage through the Picasso library and display them on a RecyclerView.

I have also tried loading the images using other libraries such as Glide and Fresco, but still the images are not showing.

Please find below the code for this application:

BlogRecyclerAdapter.java:

public BlogRecyclerAdapter(Context context, List<Blog> blogList) {
        this.context = context;
        this.blogList = blogList;
        this.firebaseStorage = firebaseStorage;
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.post_row, parent, false);

        return new ViewHolder(view, context);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

        Blog blog = blogList.get(position);
        //String imageUrl = null;

        holder.title.setText(blog.getTitle());
        holder.desc.setText(blog.getDesc());


        DateFormat dateFormat = DateFormat.getDateInstance();
        String formattedDate = dateFormat.format(new Date(Long.valueOf(blog.getTimestamp())).getTime());

        holder.timestamp.setText(formattedDate);

        String imageUrl = blog.getImage();

        //TODO: Use Picasso library to load image

        Picasso.get()
                .load(imageUrl)
                .into(holder.image);
    }

    @Override
    public int getItemCount() {
        return blogList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView title;
        public TextView desc;
        public TextView timestamp;
        public ImageView image;
        String userid;

        public ViewHolder(View view, Context ctx) {
            super(view);


            title = (TextView) view.findViewById(R.id.postTitleList);
            desc = (TextView) view.findViewById(R.id.postTextList);
            image = (ImageView) view.findViewById(R.id.postImageList);
            timestamp = (TextView) view.findViewById(R.id.timestampList);

            userid = null;

            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // we can go to the next activity...

                }
            });

        }
    }
}

PostListActivity.java (The activity in which the recyclerView is displayed):

public class PostListActivity extends AppCompatActivity {
    private DatabaseReference mDatabaseReference;
    private RecyclerView recyclerView;
    private BlogRecyclerAdapter blogRecyclerAdapter;
    private List<Blog> blogList;
    private FirebaseDatabase mDatabase;
    private FirebaseUser mUser;
    private FirebaseAuth mAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_post_list);

        mAuth = FirebaseAuth.getInstance();
        mUser = mAuth.getCurrentUser();

        mDatabase = FirebaseDatabase.getInstance();
        mDatabaseReference = mDatabase.getReference().child("MBlog");
        mDatabaseReference.keepSynced(true);

        blogList = new ArrayList<>();

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
            case R.id.action_add:
                if (mUser != null && mAuth != null) {
                    startActivity(new Intent(PostListActivity.this, AddPostActivity.class));
                    finish();
                }
                break;
            case R.id.action_signout:

                if (mUser != null && mAuth != null) {
                    mAuth.signOut();
                    startActivity(new Intent(PostListActivity.this, MainActivity.class));
                    finish();
                }
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onStart() {
        super.onStart();

        mDatabaseReference.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

                Blog blog = dataSnapshot.getValue(Blog.class);

                blogList.add(blog);

                Collections.reverse(blogList);


                blogRecyclerAdapter = new BlogRecyclerAdapter(PostListActivity.this, blogList);
                recyclerView.setAdapter(blogRecyclerAdapter);
                blogRecyclerAdapter.notifyDataSetChanged();
            }

            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {

            }

            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {

            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });
}

Blog.java (Model) :

public class Blog {

    public String title;
    public String desc;
    public String image;
    public String timestamp;
    public String userid;

    public Blog() {
    }

    public Blog(String title, String desc, String image, String timestamp, String userid) {
        this.title = title;
        this.desc = desc;
        this.image = image;
        this.timestamp = timestamp;
        this.userid = userid;
    }


    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }
}

post_row.xml :

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_margin="5dp">



    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">


        <ImageView
            android:id="@+id/postImageList"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:cropToPadding="false"
            android:scaleType="centerCrop"
            android:src="@mipmap/add_btn"/>


        <TextView
            android:id="@+id/postTitleList"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="Post Title"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/postTextList"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:paddingTop="15dp"
            android:padding="10dp"
            android:text="Post Description Here"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/timestampList"
            android:text="Date Created"
            android:padding="5dp"
            android:textStyle="italic"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</android.support.v7.widget.CardView>

activity_post_list.xml:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.myblogapp.Activities.PostListActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginRight="8dp"
        android:layout_marginLeft="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="8dp" />
</android.support.constraint.ConstraintLayout>

build.gradle(app):

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.myblogapp"
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }


    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        implementation 'com.google.firebase:firebase-core:16.0.8'
        implementation 'com.google.firebase:firebase-core:16.0.8'
        implementation 'com.google.firebase:firebase-database:16.1.0'
        implementation 'com.firebaseui:firebase-ui-storage:4.3.1'
        implementation 'com.google.firebase:firebase-auth:16.2.0'
        implementation 'com.google.firebase:firebase-storage:16.0.4'
        implementation 'com.github.bumptech.glide:glide:3.7.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        implementation 'com.android.support:recyclerview-v7:28.0.0'
        implementation 'com.android.support:cardview-v7:28.0.0'
        implementation 'com.squareup.picasso:picasso:2.71828'
        implementation 'com.theartofdev.edmodo:android-image-cropper:2.4.+'
        implementation 'com.facebook.fresco:fresco:1.10.0'
        implementation 'com.firebaseui:firebase-ui-storage:4.3.2'
    }


    apply plugin: 'com.google.gms.google-services'
}

While running the code, I also came across this error message:

E/RecyclerView: No adapter attached; skipping layout

If you find something wrong with my code, please let me know and I would be looking forward to your response.

Areen
  • 11
  • 6
  • give me image url, i'll check for load issue, and set you adapter first and then get the list and then set the new list through getter and setter in adapter and call notifydatasetchanged – hemen May 19 '19 at 17:26
  • change `android:layout_height="match_parent"` to `android:layout_height="wrap_content"` in your recyclerview. – Mohammed Farhan May 20 '19 at 07:16
  • I'm trying to get many images from firebase storage, not just one, so what url should I give you ? – Areen May 20 '19 at 08:18

2 Answers2

1

The problem is in the order the events are carried out - the adapter needs to be set to the recycler view in onCreate(). It may not be a problem with your image library after all.

I hope you'll understand that it's hard to recreate the problem (because I'd need to create a test Firebase database etc) and be 100% sure of the answer I'd strongly advise re-arranging the adapter code.

1) Move these two lines to the bottom of onCreate() in PostListActivity:

  blogRecyclerAdapter = new BlogRecyclerAdapter(PostListActivity.this, blogList);
            recyclerView.setAdapter(blogRecyclerAdapter);

2) Add a method to the BlogRecyclerAdapter class, which allows the update of items set to the adapter:

public void updateBlogList(ArrayList<Blog> newBlogs){
this.blogList = newBlogs;
notifyDataSetChanged();  }
  1. In the onChildAdded() method, remove the three lines of code that were moved to onCreate() in Step 1 above, so that onChildAdded() contains:

    Blog blog = dataSnapshot.getValue(Blog.class);
    
    blogList.add(blog);
    
    Collections.reverse(blogList);
    
    blogRecyclerAdapter.updateBlogList(blogList); 
    

As I said, it's hard to recreate the problem and there may be other issues in the code, but hopefully this will resolve the "E/RecyclerView: No adapter attached; skipping layout" error.

Also, Glide seems to be the stronger library for loading images from external sources. In one of my projects, I was able to load images within onBind() in adapter using the following code:

 mFirebaseDatabase = FirebaseDatabase.getInstance();
    mRef = mFirebaseDatabase.getReference(Constants.USERS_PATH).child(offeredRoute.getUserID());
    mFirebaseStorage = FirebaseStorage.getInstance();
    mStorageReference = mFirebaseStorage.getReference().child(Constants.IMAGES_PATH).child(offeredRoute.getUserID());

//download all other values
mRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        mUserProfile = dataSnapshot.getValue(UserProfile.class);
        //set data to views
        if (mUserProfile != null) {
            holder.resultUsernameTv.setText(mUserProfile.getUser());

            if (mUserProfile.getPhotoUrl() == null || mUserProfile.getPhotoUrl().isEmpty()) {
                Timber.v("No photo saved yet");
            } else {
                StorageReference downloadRef = mStorageReference.child(mUserProfile.getPhotoUrl());
                Glide.with(mContext)
                        .using(new FirebaseImageLoader())
                        .load(downloadRef)
                        .into(holder.ImageViewRv);
            }
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {

    }
});

Thanks

Izzy Stannett
  • 242
  • 1
  • 11
  • The error "E/RecyclerView: No adapter attached; skipping layout" goes away, but the images are still not visible. – Areen May 20 '19 at 04:50
  • What do the logs say now? Also, which dependencies do you have in `build.gradle`? – Izzy Stannett May 20 '19 at 06:40
  • I have added the build.gradle file in the question now. – Areen May 20 '19 at 06:54
  • And you have `allow read, write: if true;` saved in your Firebase Storage Rules? – Izzy Stannett May 20 '19 at 08:38
  • You have a duplication of `implementation 'com.firebaseui:firebase-ui-storage:4.3.x` in your build.gradle – Izzy Stannett May 20 '19 at 08:41
  • Yes, I have `allow read, write: if true;` saved in my Firebase Storage rules. – Areen May 20 '19 at 16:20
  • Ok. And if you Log out the image urls from within your adapter, what do you see? Are they pulling through successfully? `String imageUrl = blog.getImage();` `Log.i(TAG, "ImageUrl: " + imageUrl);` – Izzy Stannett May 20 '19 at 17:14
  • No, the image URLs don't get displayed in the LogCat. – Areen May 21 '19 at 04:56
  • do you have a reference to Firebase Storage? I can't see it. I've added the code that I used in my adapter to my answer, maybe this will help you. I get references to Firebase from the adapter. The dataset I pass in is and ArrayList<>() of Users, which would be blogs in your case – Izzy Stannett May 21 '19 at 07:28
  • What should I put in place of getPhotoUrl since it says: Cannot resolve method 'getPhotoUrl()' ? Should I put getImage() in place of it? – Areen May 21 '19 at 08:15
  • Your Blog equivalent is getImage() – Izzy Stannett May 21 '19 at 08:20
  • When I add Glide.with(context) .using(new FirebaseImageLoader()) .load(downloadRef) .into(holder.image); to my code, it says that it cannot resolve the method 'using'. – Areen May 21 '19 at 08:43
  • Take a look at this, you need to change your Glide version https://stackoverflow.com/questions/44949432/how-to-integrate-firebase-with-glide-using-method – Izzy Stannett May 21 '19 at 09:00
  • And what should be the values of Constants.USERS_PATH and Constants.IMAGES_PATH – Areen May 21 '19 at 09:05
  • They're the names of the Firebase Storge and Firebase Database folders that you wish to get the images and data from. So for me, these were called "users" and "images", which I saved as constants elsewhere. This totally depends on how youve set up your database/storage. You're aiming for a direct reference to the files/data – Izzy Stannett May 21 '19 at 09:27
  • The images are still not showing, but there's no error displayed in the LogCat. – Areen May 21 '19 at 12:08
0

Looks like your childlistner is not working. Instead of the ChildEventListener try the ValueEventListener. Find out more about the listeners through the firebase doc

mDatabaseReference.addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot dataSnapshot) {
    for (DataSnapshot data : dataSnapshot.getChildren()){
       Blog blog = dataSnapshot.getValue(Blog.class);
       blogList.add(blog);
    }
    Collections.reverse(blogList);
    blogRecyclerAdapter = new BlogRecyclerAdapter(PostListActivity.this, blogList);
    recyclerView.setAdapter(blogRecyclerAdapter);
  }

  @Override
  public void onCancelled(DatabaseError databaseError) {
    System.out.println("The read failed: " + databaseError.getCode());
  }
});
sanoJ
  • 2,935
  • 2
  • 8
  • 18