-2

I'm using Retrofit to load data from web api and display data to recyclerview. Here are my layout:

Item of recycler view:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="10dp"
        android:layout_weight="30"

        >
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                xmlns:cardview="http://schemas.android.com/apk/res-auto"
                cardview:cardCornerRadius="@dimen/cardview_corner_radius"
                >
                <ImageView
                    android:id="@+id/imgExhibit"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="fitXY"

                    android:src="@drawable/img_no_image" />
            </android.support.v7.widget.CardView>
            <ProgressBar
                android:id="@+id/viewProgressBar"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:indeterminateDrawable="@drawable/my_progress"
                android:visibility="gone"/>
        </RelativeLayout>



    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="70"
        android:orientation="vertical"
        android:weightSum="100">

        <TextView
            android:id="@+id/tvExhibitName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"

            android:layout_weight="10"
            android:text="TRỐNG NHẠC"
            android:textAppearance="@style/ExhibitNameMainscreen" />

        <TextView
            android:id="@+id/tvExhibitDescription"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="@style/ExhibitDescriptionMainscreen"
            android:layout_weight="30"
            android:ellipsize="end"
            android:maxLines="2"
            android:text="Trống nhạc là hiện vật do Toà thánh Ngọc Sắc trao tặng"
             />

    </LinearLayout>


</LinearLayout>

Model is auto parse by http://www.jsonschema2pojo.org/ from api: http://demo.museum.vebrary.vn/api/Exhibit/GetPaging?pageindex=1&pagesize=10

public class ExhibitMainScreenModel {

@SerializedName("EXHID")
@Expose
private Integer eXHID;
@SerializedName("EXHIBITNAME")
@Expose
private String eXHIBITNAME;
@SerializedName("DESCRIPTION")
@Expose
private String dESCRIPTION;

public Integer getEXHID() {
    return eXHID;
}

public void setEXHID(Integer eXHID) {
    this.eXHID = eXHID;
}

public String getEXHIBITNAME() {
    return eXHIBITNAME;
}

public void setEXHIBITNAME(String eXHIBITNAME) {
    this.eXHIBITNAME = eXHIBITNAME;
}

public String getDESCRIPTION() {
    return dESCRIPTION;
}

public void setDESCRIPTION(String dESCRIPTION) {
    this.dESCRIPTION = dESCRIPTION;
}

}

and api http://demo.museum.vebrary.vn/api/Exhibit/GetImages?id=4 return one String of image by id

Previously, I used to add attribute String Image to Model because the previous api return both data and image. And I can load it easy to recycler view. But it toke many time loading. And then, I have 2 api get data and get image independent. I want to display data before, and UI main don't block by loading image to image view, I want to can scroll the recycler view while the image view loading (be like facebook app) Here are Main activity class:

public class MainActivityNew extends AppCompatActivity implements View.OnClickListener {
Toolbar toolbar;
RecyclerView recyclerView;
NavigationView navigationView;
DrawerLayout drawerLayout;
TextView tvTitleToolbar, txtTitleCategory;

//RecyclerView api
private ExhibitMainscreenRecyclerViewAdapter mAdapter;
private ApiService mService;
//ProgressDialog
private ProgressBar viewProgressBar;
private CustomProgressDialogTwo customProgressDialogTwo;
private LoadMoreProgressDialog loadMoreProgressDialog;
//load more
private int indexPage=1;
private int size=10;

private EndlessRecyclerViewScrollListener scrollListener;
//
int id;



@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_new);
    //set transparent stt bar
    //StatusBarUtil.setTransparent(this);
    addControl();
    actionBar();
    showDataToRecyclerView();
    showProgressDialog();
    loadAnswers(indexPage,size);
    showIntroMenu();
    setPositionTextViewTittleCategogy();
    addEvent();




}



public void showErrorMessage() {
    Toast.makeText(MainActivityNew.this, "Error loading posts", Toast.LENGTH_SHORT).show();
}

private void showProgressDialog() {
    viewProgressBar.setVisibility(View.GONE);
    customProgressDialogTwo.show();
}
private void showLoadMoreProgressDialog(){
    viewProgressBar.setVisibility(View.GONE);
    loadMoreProgressDialog.show();
}
private void loadAnswers(int indexPage,int size) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            mService.getExhibitByPage(indexPage,size).enqueue(new Callback<AllExhibitJsonResponse>() {
                @Override
                public void onResponse(Call<AllExhibitJsonResponse> call, Response<AllExhibitJsonResponse> response) {

                    if(response.isSuccessful()) {
                        customProgressDialogTwo.dismiss();
                        mAdapter.updateAnswers(response.body().getExhibitModels());
                        Log.d("AnswersPresenter", "posts loaded from API");
                    }else {
                        int statusCode  = response.code();
                        Toast.makeText(MainActivityNew.this, "Error"+statusCode+response.message(), Toast.LENGTH_SHORT).show();
                    }
                }

                @Override
                public void onFailure(Call<AllExhibitJsonResponse> call, Throwable t) {
                    showErrorMessage();
                    Log.d("AnswersPresenter", "error loading from API");

                }
            });
        }
    }).start();

}
private void showDataToRecyclerView() {
    mAdapter = new ExhibitMainscreenRecyclerViewAdapter(this, new ArrayList<ExhibitMainScreenModel>(0), new ExhibitMainscreenRecyclerViewAdapter.PostItemListener() {


        @Override
        public void onPostClick(long id) {
            startDetailActivity((int)id);

        }
    });
    /*RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);*/
    GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(mAdapter);
    recyclerView.setHasFixedSize(true);
    EndlessRecyclerViewScrollListener scrollListener = new EndlessRecyclerViewScrollListener(layoutManager) {

        @Override
        public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
            showLoadMoreProgressDialog();
            loadMoreAnswers(page,size);


        }
    };
    recyclerView.addOnScrollListener(scrollListener);
}

private void loadMoreAnswers(int i, int size) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            mService.getExhibitByPage(i+1,size).enqueue(new Callback<AllExhibitJsonResponse>() {
                @Override
                public void onResponse(Call<AllExhibitJsonResponse> call, Response<AllExhibitJsonResponse> response) {

                    if(response.isSuccessful()) {
                        loadMoreProgressDialog.dismiss();
                        mAdapter.updateMoreAnswers(response.body().getExhibitModels());

                        Log.d("AnswersPresenter", "posts loaded from API");
                    }else {
                        int statusCode  = response.code();
                        Toast.makeText(MainActivityNew.this, "Error"+statusCode+response.message(), Toast.LENGTH_SHORT).show();
                    }
                }

                @Override
                public void onFailure(Call<AllExhibitJsonResponse> call, Throwable t) {
                    showErrorMessage();
                    Log.d("AnswersPresenter", "error loading from API");

                }
            });
        }
    }).start();



}

And the adapter:

public class ExhibitMainscreenRecyclerViewAdapter extends RecyclerView.Adapter<ExhibitMainscreenRecyclerViewAdapter.ViewHolder> {

private List<ExhibitMainScreenModel> ExhibitList;
private Context mContext;
private PostItemListener mItemListener;
private int id;
//web api
private ApiService mService;


public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

    public TextView tvName,tvDescription;
    public ImageView imvExhibit;
    PostItemListener mItemListener;

    public ViewHolder(View itemView, PostItemListener postItemListener) {
        super(itemView);
        tvName = itemView.findViewById(R.id.tvExhibitName);
        tvDescription = itemView.findViewById(R.id.tvExhibitDescription);
        imvExhibit=itemView.findViewById(R.id.imgExhibit);

        this.mItemListener = postItemListener;
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        ExhibitMainScreenModel item = getItem(getAdapterPosition());
        this.mItemListener.onPostClick(item.getEXHID());

        notifyDataSetChanged();
    }
}

public ExhibitMainscreenRecyclerViewAdapter(Context context, List<ExhibitMainScreenModel> posts, PostItemListener itemListener) {
    ExhibitList = posts;
    mContext = context;
    mItemListener = itemListener;
}

@Override
public ExhibitMainscreenRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    Context context = parent.getContext();
    LayoutInflater inflater = LayoutInflater.from(context);

    View postView = inflater.inflate(R.layout.item_recyclerview_mainscreen, parent, false);

    ViewHolder viewHolder = new ViewHolder(postView, this.mItemListener);
    //web api
    mService = ApiUtils.getSOService();
    return viewHolder;
}

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

    ExhibitMainScreenModel item = ExhibitList.get(position);
    TextView tvName = holder.tvName;
    tvName.setText(item.getEXHIBITNAME());
    TextView tvDesc = holder.tvDescription;
    tvDesc.setText(item.getDESCRIPTION());
    //
    id = item.getEXHID();
    loadImage(id,holder);




}

private void loadImage(int id, ViewHolder holder) {
    new Thread(new Runnable() {
        @Override
        public void run() {
                //get image default
                mService.getExhibitImageById(id, true).enqueue(new Callback<String>() {
                    @Override
                    public void onResponse(Call<String> call, Response<String> response) {

                        if (response.isSuccessful()) {
                            try{
                                showImage(response.body(),holder);
                                Log.d("AnswersPresenter", "Image loaded!!!!");
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }

                        } else {
                            int statusCode = response.code();
                            Toast.makeText(mContext, "Error" + statusCode + response.message(), Toast.LENGTH_SHORT).show();
                        }

                    }


                    @Override
                    public void onFailure(Call<String> call, Throwable t) {
                        showErrorMessage();
                        Log.d("AnswersPresenter", "error loading image!!!");

                    }
                });

        }
    }).run();
}

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

public void updateAnswers(List<ExhibitMainScreenModel> items) {
    ExhibitList = items;
    notifyDataSetChanged();
}
public void updateMoreAnswers(List<ExhibitMainScreenModel> items) {
    ExhibitList.addAll(items);
    notifyDataSetChanged();
}

private ExhibitMainScreenModel getItem(int adapterPosition) {
    return ExhibitList.get(adapterPosition);
}

public interface PostItemListener {
    void onPostClick(long id);
}
private void showImage(String imageString, ViewHolder holder) {
    Bitmap bmp = Util.StringToBitMap(imageString);
    ImageView imv = holder.imvExhibit;
    imv.setImageBitmap(bmp);

}

public void showErrorMessage() {
    Toast.makeText(mContext, "Error loading posts", Toast.LENGTH_SHORT).show();
}

}

And here is result with my code. result

but the image is display intricate So, can you help me? Ah, It usually have error "android application doing too much work on its main thread" Can you help me solve it? Thank you very much!!

Phuc sino
  • 29
  • 7

2 Answers2

1

Once you get the response or url of image the kindly use the libarary by this way it will automatically handle all scenario.

if you are getting url of image in the response in below line:

showImage(response.body(),holder);

here if response.body() contains the url of image

Replace

 Bitmap bmp = Util.StringToBitMap(imageString);
    ImageView imv = holder.imvExhibit;
    imv.setImageBitmap(bmp);

by

Picasso.with(context).load(your image url).into(imageView);

if your response is base64 encoded. where urlRes is your encoded response like:-

String urlRes = "XFLSVJpiqqVA3yQVy74obNAMCrajG1a5mvtgZBcXFK";
String data =  new FileDecryption().decrypt(urlRes);
        Log.e("MEssage decoded", data);

add two classes in your project.

Base64.java

public class Base64 {
    /**
     * encode
     *
     * coverts a byte array to a string populated with
     * base64 digits.  It steps through the byte array
     * calling a helper method for each block of three
     * input bytes
     *
     * @param raw The byte array to encode
     * @return A string in base64 encoding
     */
    public static String encode(byte[] raw) {
        StringBuffer encoded = new StringBuffer();
        for (int i = 0; i < raw.length; i += 3) {
            encoded.append(encodeBlock(raw, i));
        }
        return encoded.toString();
    }

    /*
     * encodeBlock
     *
     * creates 4 base64 digits from three bytes of input data.
     * we use an integer, block, to hold the 24 bits of input data.
     *
     * @return An array of 4 characters
     */
    protected static char[] encodeBlock(byte[] raw, int offset) {
        int block = 0;
        // how much space left in input byte array
        int slack = raw.length - offset - 1;
        // if there are fewer than 3 bytes in this block, calculate end
        int end = (slack >= 2) ? 2 : slack;
        // convert signed quantities into unsigned
        for (int i = 0; i <= end; i++) {
            byte b = raw[offset + i];
            int neuter = (b < 0) ? b + 256 : b;
            block += neuter << (8 * (2 - i));
        }

        // extract the base64 digits, which are six bit quantities.
        char[] base64 = new char[4];
        for (int i = 0; i < 4; i++) {
            int sixbit = (block >>> (6 * (3 - i))) & 0x3f;
            base64[i] = getChar(sixbit);
        }
        // pad return block if needed
        if (slack < 1)
            base64[2] = '=';
        if (slack < 2)
            base64[3] = '=';
        // always returns an array of 4 characters
        return base64;
    }

    /*
     * getChar
     *
     * encapsulates the translation from six bit quantity
     * to base64 digit
     */
    protected static char getChar(int sixBit) {
        if (sixBit >= 0 && sixBit <= 25)
            return (char) ('A' + sixBit);
        if (sixBit >= 26 && sixBit <= 51)
            return (char) ('a' + (sixBit - 26));
        if (sixBit >= 52 && sixBit <= 61)
            return (char) ('0' + (sixBit - 52));
        if (sixBit == 62)
            return '+';
        if (sixBit == 63)
            return '/';
       return '?';
    }

    /**
     * decode
     *
     * convert a base64 string into an array of bytes.
     *
     * @param base64 A String of base64 digits to decode.
     * @return A byte array containing the decoded value of
     *         the base64 input string
     */
    public static byte[] decode(String base64) {
        // how many padding digits?
        int pad = 0;
        for (int i = base64.length() - 1; base64.charAt(i) == '='; i--)
            pad++;
        // we know know the length of the target byte array.
        int length = base64.length() * 6 / 8 - pad;
        byte[] raw = new byte[length];
        int rawIndex = 0;
        // loop through the base64 value.  A correctly formed
        // base64 string always has a multiple of 4 characters.
        for (int i = 0; i < base64.length(); i += 4) {
            int block = (getValue(base64.charAt(i)) << 18)
                    + (getValue(base64.charAt(i + 1)) << 12)
                    + (getValue(base64.charAt(i + 2)) << 6)
                    + (getValue(base64.charAt(i + 3)));
            // based on the block, the byte array is filled with the
            // appropriate 8 bit values
            for (int j = 0; j < 3 && rawIndex + j < raw.length; j++)
                raw[rawIndex + j] = (byte) ((block >> (8 * (2 - j))) & 0xff);
            rawIndex += 3;
        }
        return raw; 
    }

    /*
     * getValue
     *
     * translates from base64 digits to their 6 bit value
     */
    protected static int getValue(char c) {
        if (c >= 'A' && c <= 'Z')
            return c - 'A';
        if (c >= 'a' && c <= 'z')
            return c - 'a' + 26;
        if (c >= '0' && c <= '9')
            return c - '0' + 52;
        if (c == '+')
            return 62;
        if (c == '/')
            return 63;
        if (c == '=')
            return 0;
        return -1;
    }
}

and second one is FileDecryption.java

public class FileDecryption {

     private static final String ALGO = "AES";
    private static final byte[] keyValue = 
        new byte[] { 'S', 'n', 'd', 'A', 'p', 'p','s','S', 'e', 'c', 'r','e', 't', 'K', 'e', 'y' };

    public  String decrypt(String encryptedData) {
        //encryptedData="PDvLeKmmKUcUQd/zKZ5b2JPRCXHJwu4dVZhaZJCrRK4JRvBP3IhRpizk6qCLcaUmhoDGR+QUeS1fjHdWujZTGQ==";
        String decryptedValue=null;
        //int flags= Base64.NO_WRAP | Base64.URL_SAFE;
        if("ON".equals("ON")){
        try{
        Key key = generateKey();
        Cipher c = Cipher.getInstance(ALGO);
        c.init(Cipher.DECRYPT_MODE, key);
       // byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
        byte[] decordedValue = Base64.decode(encryptedData);
        byte[] decValue = c.doFinal(decordedValue);
        decryptedValue = new String(decValue);}
        catch(Exception e){
            e.printStackTrace();
        }
        }else{
            decryptedValue=encryptedData;
        }

        //Log.d("decrypted : ", decryptedValue);
        return decryptedValue;
    }
    private static Key generateKey() throws Exception {
        Key key = new SecretKeySpec(keyValue, ALGO);
        return key;
}

}
  • http://demo.museum.vebrary.vn/api/Exhibit/GetImage?id=3 The response.body() is a String. Can I use Picasso to load it? or must change from string to what? – Phuc sino Jun 08 '18 at 01:09
  • you can put the url of image, if response.body() contains url of image just put the url inside of load method. Suppose your url is [link]("http://xyz.png" ). then you have to write Picasso.with(context).load( "http://xyz.png").into(imageView); – Gyan Swaroop Awasthi Jun 08 '18 at 06:01
  • load() method takes only url as parameter. if you are receiving the URL of image in your server response then just parse the response and put the url of image inside load("URL OF IMAGE"). Suppose your url of image is [link](http://example.com) then use Picasso.with(context).load(http://example.com).into(imageView); – Gyan Swaroop Awasthi Jun 08 '18 at 06:27
  • but my response return a base64 encoded string. And load() method don't support it – Phuc sino Jun 08 '18 at 06:29
  • Ok first you need to encode the response of Base64 , i saw your response it is encoded , first you encode the response then you will get the original response and you get the image url. Just take the reference import java.util.Base64; byte[] bytes = "Hello, World!".getBytes("UTF-8"); String encoded = Base64.getEncoder().encodeToString(bytes); byte[] decoded = Base64.getDecoder().decode(encoded); [link](https://stackoverflow.com/questions/469695/decode-base64-data-in-java) – Gyan Swaroop Awasthi Jun 08 '18 at 06:35
  • @Phucsino i have updated the answer kindly check , if you have any problem please let me know. – Gyan Swaroop Awasthi Jun 08 '18 at 07:56
  • private void showImage(String imageString, ViewHolder holder) { ImageView imv = holder.imvExhibit; String urlRes = imageString; String data = new FileDecryption().decrypt(urlRes); Log.e("MEssage decoded", data); Picasso.with(mContext).load(data).into(imv); } . I have change my showImage() funtion,Is it correctly? it don't working – Phuc sino Jun 08 '18 at 08:16
  • logcat: W/System.err: javax.crypto.IllegalBlockSizeException: last block incomplete in decryption – Phuc sino Jun 08 '18 at 08:22
  • In private void showImage(String imageString, ViewHolder holder) {} Please tell me what is the value of String imageString ? – Gyan Swaroop Awasthi Jun 08 '18 at 09:12
  • value of imageString is a encode string: "/9j/4AAQSkZJRgABAgEASABIA.." in the return of api http://demo.museum.vebrary.vn/api/Exhibit/GetImage?id=3 – Phuc sino Jun 08 '18 at 09:29
  • private void showImage(String imageString, ViewHolder holder) { String data = new FileDecryption().decrypt(imageString); Log.e("MEssage decoded", data); //After decoding the "data " will contains the original response find the url of image and use Picaso library } – Gyan Swaroop Awasthi Jun 08 '18 at 10:06
  • private void showImage(String imageString, ViewHolder holder) { ImageView imv = holder.imvExhibit; String data = new FileDecryption().decrypt(imageString); Picasso.with(mContext).load(data).into(imv); – Phuc sino Jun 08 '18 at 10:39
  • and error: System.err: javax.crypto.IllegalBlockSizeException: last block incomplete in decryption – Phuc sino Jun 08 '18 at 10:40
  • at row: byte[] decValue = c.doFinal(decordedValue); in FileDecryption.java – Phuc sino Jun 08 '18 at 10:41
  • and at row: String data = new FileDecryption().decrypt(imageString); – Phuc sino Jun 08 '18 at 10:41
  • dear first you have to decode base64 response , for decoding you can search for google no of solutions available. Then you have to parse your response , for parsing you have to check whather your response is of xml or json, if json then you have to parse the json and if xml then you have to parse xml and get URL as string and then you have pass that string to load() method. this is whole process. You can not pass the whole response to load() method. – Gyan Swaroop Awasthi Jun 08 '18 at 12:42
  • I am giving general example : suppose you have no of things in plastic bag (consider it as encoded response) inside a basket and you have to choose apple (as url) then first you have to remove plastic bag(decode the response) then you have to find the apple(by parsing of response) , once you get apple(image url from decoded response) then you can use that apple (then you can use load() wiith url).. OK – Gyan Swaroop Awasthi Jun 08 '18 at 12:42
0

If I understood you correctly, you're using retrofit to load the image from the network. Instead of doing it yourself, there are some few popular libraries intended to handle, load and display images whithin an app, giving the user a free UI thread and experience. These libraries let you focus on your main logic instead of technical things every developer needs. Also, because they are very popular, they are less buggy than what we all would probably write ourselves and keep on being updated as time goes by. These libraries include, among others, Picasso, Glide and Fresco.

Good luck!

TheCodeFather
  • 194
  • 1
  • 7
  • [link] (http://demo.museum.vebrary.vn/api/Exhibit/GetImage?id=3) The response.body() is a String Base64 encoded. Which can to load it? or must change from string to what? – Phuc sino Jun 08 '18 at 01:25
  • You only need your model to hold the image url from which to download the image. Then you just give the image library, say Picasso, the image url and the image view into which to load the image once it is finished downloading. Picasso will do it all for you. So in your case, you would just give Picasso the demo.museum.../getimage?Id=3 url and the image view of the view holder you wish to populate. – TheCodeFather Jun 08 '18 at 02:52
  • but my response return a base64 encoded string. And load() method don't support it – Phuc sino Jun 08 '18 at 06:57