I first make an async task to retrieve JSON data. One of the JSON objects retrieved is an URL.
// This goes on my asynctask to get JSON data from the server
#Override
public void onPostExecute(JSONObject json) {
try {
String title = json.getString("title");
String description = json.getString("description");
String url = json.getString("url");
// Here comes the tricky part
Bitmap bitmap = Bitmap.createBitmap(100,100,Bitmap.Config.ARGB_8888);
new DownloadBitmap(bitmap).execute(url);
myArrayList.add(new DataContainer(title,description,bitmap));
// End of the tricky part
}catch(Exception e) {}
adapter.NotifyDataSetChange();
}
OK as you can see I need a reference of bitmap to send to the downloader class and to the arraylist for the listview's adapter.
The problem is that the downloader class goes like this:
class DownloadBitmap extends AsyncTask<the 3 paramateres goes here>{
Bitmap bitmap;
public DownloadBitmap(Bitmap b) {
//Here I have a reference to the SAME bitmap object I added to the arraylist
bitmap = b;
}
#Override
protected void doInBackground(String... Params) {
// some code.....
// THIS IS THE PROBLEM, it re-initialize.
bitmap = BitmapFactory.decodeStream(inputstream);
}
I wasn't able to find any other way around, and If possible I'd like a solution library independant, my company has strict policy over software dependance.
You can use Universal Image Loader Library.
link
Related
The following is the code for the adapter I'm using to display movie items that contain Movie Images and Movie Titles.
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MovieViewHolder> {
private List<Movie> movieList;
public MovieAdapter(List<Movie> movieList) {
this.movieList = movieList;
}
#Override
public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.movie_item, parent, false);
return new MovieViewHolder(view);
}
#Override
public void onBindViewHolder(final MovieViewHolder holder, int position) {
Movie movie = movieList.get(position);
holder.mv_name.setText(movie.getName());
class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
#Override
protected Bitmap doInBackground(String... strings) {
Bitmap bitmap = null;
URL url = createUrl(strings[0]);
Log.v("stringurl", strings[0]);
try {
bitmap = makeHttpRequest(url);
} catch (IOException e) {
}
return bitmap;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
holder.mv_img.setImageBitmap(bitmap);
}
private URL createUrl(String StringUrl) {
URL url = null;
try {
url = new URL(StringUrl);
} catch (MalformedURLException e) {
return null;
}
return url;
}
private Bitmap makeHttpRequest(URL url) throws IOException {
HttpURLConnection urlConnection = null;
InputStream stream = null;
Bitmap bitmap = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setConnectTimeout(10000);
urlConnection.setReadTimeout(15000);
urlConnection.connect();
stream = urlConnection.getInputStream();
bitmap = BitmapFactory.decodeStream(stream);
//Log.v("image:",bitmap.toString());
return bitmap;
} catch (IOException e) {
} finally {
if (urlConnection != null) urlConnection.disconnect();
if (stream != null) stream.close();
}
return bitmap;
}
}
new ImageDownloadTask().execute(movie.getImg());
}
#Override
public int getItemCount() {
return movieList == null ? 0 : movieList.size();
}
class MovieViewHolder extends RecyclerView.ViewHolder {
ImageView mv_img;
TextView mv_name;
public MovieViewHolder(View itemView) {
super(itemView);
mv_img = (ImageView) itemView.findViewById(R.id.mv_img);
mv_name = (TextView) itemView.findViewById(R.id.mv_name);
}
}
}
While scrolling down images are loading images of other movies are displaying. For example, for a movie, another movie image is getting displayed and after a second also another movie image is getting displayed in that way after a series of different images correct image is getting displayed.
I'm downloading images from the Url I get from the response inBindViewHolder method.
How to resolve this issue?
Problem
The thing here is to realize that the ViewHolders are pool of reused objects and as you scroll a reused ViewHolder will appear and the previously loaded in it image will be shown.
By using Picasso or Glide or similar you are saving incredible amount of code also using additional features like cache and effects.
Solution
First you can use a library like Picasso
compile 'com.squareup.picasso:picasso:2.5.2'
then in your recyclerview on bind viewholder
Picasso.with(holder.imageView.getContext()).cancelRequest(holder.imageView);
Picasso.with(holder.imageView.getContext()).load("http://image.com/image.png").into(holder.imageView);
The first line of code will stop any previous images from loading.
The second line will load the current image.
Replace "http://image.com/image.png" with your image url. It takes String, URL, etc...
Read more about Picasso here
Problem
The root cause for your problem is that the bitmap is retrieved after the view has been recycled and thus you get a mismatch when you use it.
How to resolve
The short answer is to use an image caching library like Picasso or Glide. These libraries where designed to do exactly what you are trying to accomplish.
Another option is to try to do this on your own. What you can do is save the URL inside the holder and call setImageBitmap() only in case the current URL matches the one you just fetched. To save the URL, just add a URL field in MovieViewHolder and store the URL whenever you retrieve the image and check that it matches when you finish downloading.
Use Picasso or Glide Library to display images in RecyclerView
i had also searched for this solution and from Doron Yakovlev-Golani answer i made a solution for my own, as my reputation is low so i'm unable to add a comment. Below is my solution, here when i added an image in ImageView I also added a ContentDescription to the ImageView as a reference for future use to determine which image need to show when ViewHolder call. The ContentDescription is current ListView position as String.
Here is two condition, first one for checking image previously added or not, if image not added previously, I added the image. second one image already added in the ImageView, so I checking which image need to load by calling ImageView ContentDescription, if ContentDescription matched current position it means i need to add current positions image to ImageView from my list.
In your onViewHolder you can use like this:
//checking it is first time or not
if (holder.mv_img.getContentDescription() == null) {
//adding current position for loading current position image
imageView.setContentDescription(position + "");
}
if (holder.mv_img.getContentDescription().equals(position + "")) {
holder.mv_img.setImageBitmap(userLogoUri);
}
in my app use want to put instagram profile pic in his account
how can I get the Profile Picture of a user from Instagram Programmatically
such as :
Using the Instagram API users endpoint (https://api.instagram.com/v1/users/{user-id}/?access_token=ACCESS-TOKEN
) you will receive a response like this one:
{
"data": {
"id": "1574083",
"username": "snoopdogg",
"full_name": "Snoop Dogg",
"profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
"bio": "This is my bio",
"website": "http://snoopdogg.com",
"counts": {
"media": 1320,
"follows": 420,
"followed_by": 3410
}
}
You can then take the profile_picture and download it using some like Facebook Fresco which will then display the image for you.
Well if you actually want the user to POST a instagram profile picture from your app you can't, instagram hasn't provided the post method for posting photos but yes you can get view and download as per the husky's answer.
Follow these steps to get profile pic in your app
Make a request to below link with username and receive the JSON data.
https://apinsta.herokuapp.com/u/USERNAME
Now parse the id of user from the data.
Now visit this link with the ID
https://i.instagram.com/api/v1/users/ID_HERE/info/
Now parse url key in hd_profile_pic_url_info object.
Load the image in ImageView using Glide.
Happy coding !
Credit to Chrisby for his server.
https://stackoverflow.com/a/49862390/9565955
Actually I'm using a method that doesn't require an Access Token the only thing your need is an username. I'll leave you the code down bellow
Step 1: MainActivity.java
Add this code in to your main activity, create a String function to named getHDProfilePicFromUsername the fuction should look like this:
///This function will return to you the url of the user profile picture
private String getHDProfilePicFromUsername(String username) throws ExecutionException, InterruptedException, JSONException {
JSONObject jObject;
String profileInfo = new getDataFromUrl(MainActivity.this).execute("https://www.instagram.com/"+username+"/?__a=1").get();
jObject= new JSONObject(profileInfo);
jObject = jObject.getJSONObject("graphql");
jObject = jObject.getJSONObject("user");
String response = jObject.getString("profile_pic_url_hd");
return response;
}
Then create an internal class inside your Activity named getDataFromUrl, the class should look like this:
private class getDataFromUrl extends AsyncTask<String, Void, String> {
Context mContext;
public getDataFromUrl(Context mContext) {
this.mContext = mContext;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(String... arg0) {
HttpHandler sh = new HttpHandler();
// Making a request to url and getting response
String jsonStr = sh.makeServiceCall(arg0[0]);
Log.e(TAG, "Response from url: " + jsonStr);
if (jsonStr != null) {
return jsonStr;
} else {
Log.e(TAG, "Couldn't get json from server.");
return null;
}
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
String url = s.replace('|',',').split(",")[1];
Log.d(TAG, "Link: " + url);
}
}
Now you are able to get an user profile picture URL, as a bonus I'll let you the code for using that URL and get the picture in to an ImageView.
Step 2: main_activity.xml
Add an image view, the ImageView, can be custom but I recommend 1:1 scale to keep image quality. Your xml should look like this:
<ImageView
android:id="#+id/imgProfilePic"
android:scaleType="fitXY"
android:src="#drawable/ic_image_black_24dp"
android:layout_width="300dp"
android:layout_height="300dp"/>
</RelativeLayout>
Step 3: DownloadImageTask.java
Now you need to create an external class called DownloadImageTask the full code have to be like this one:
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
import java.io.InputStream;
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
Step 4: MainActivity.java
Now the last step will be to add your Main Activity the piece of code to load the ImageView, this code will be in your OnCreate void inside your MainActivity.java
ImageView = thumbnails = (ImageView)findItemById(R.id.imgProfilePic);
new DownloadImageTask(thumbnails).execute(getHDProfilePicFromUsername("jusnjshb"));
That's all, hope it helps I have 2 years using this code hope it helps.
So I'm currently investigating the possibility to replace a lot of custom image loading AsyncTasks in a project, with the Picasso library and it seems promising. However there is one issue that I'm not completely sure how to solve using Picasso.
In this case we are downloading Album Art for music tracks, however when our ListView is to be shown we only have the track ids. So first we have to lookup an Album Art URL based on the track id and then load it into an ImageView. Currently we have an AsyncTask where we in doInBackground() first lookup the image URL and then load a Bitmap from it, which is then pasted to onPostExecute.
Is there any way to make this pre-lookup using Picasso or will we have to wrap the Picasso call in a AsyncTask that first perform the lookup (and feels like it kinda defeats the purpose).
Update: How it works now:
private class AlbumArtTask extends AsyncTask<String, Bitmap, Bitmap> {
#Override
protected Bitmap doInBackground(String... strings) {
final String id = strings[0];
// This part is what I'm asking for, basically to be able to make an Async
// lookup of the url that will later be used by Picasso
final Uri uri = getAlbumArtUriFromTrackId(id);
// Creating the Bitmap and return it to be used in an ImageView
// This part is handled by Picasso
return bitmap = createBitmapFromUri(uri);
}
#Override
protected void onPostExecute(Bitmap bitmap) {
// load image into ImageView
}
}
Update 2: Made up example of what I'm after
Picasso.with(mContext)
.load(new UriProvider() {
// Get the actual image URI here
public Uri getImageUri() {
return getAlbumArtUriFromTrackId(id);
}
})
// And load it here
.into(mImageView);
I had to solve the same problem for my app. I used OkHTTP interceptor to redirect the request.
This is my picassa declaration:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new PhotoInterceptor());
Picasso picasso = new Picasso.Builder(context)
.downloader(new OkHttpDownloader(client))
.build();
This is a basic implementation of the interceptor:
public class PhotoInterceptor implements Interceptor {
Gson gson = new Gson();
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (response.isSuccessful()) {
Photo photo = gson.fromJson(response.body().charStream(), Photo.class);
if (photo!=null) {
request = request.newBuilder()
.url(photo.getPhoto_file_url())
.build();
response = chain.proceed(request);
}
}
return response;
}
}
You have to load images from your Album Art URL using Piccaso while your listView is created. Then on Detail screen of Track you have to call your actual image url to set it in imageview.
Edited
private class AlbumArtTask extends AsyncTask<String, Bitmap, Uri> {
ImageView mImageView;
Context mContext;
public AlbumArtTask(Context context,ImageView imageView){
this.mImageView=imageView;
this.mContext=context;
}
#Override
protected Uri doInBackground(String... strings) {
final String id = strings[0];
// This part is what I'm asking for, basically to be able to make an Async
// lookup of the url that will later be used by Picasso
final Uri uri = getAlbumArtUriFromTrackId(id);
// Creating the Bitmap and return it to be used in an ImageView
// This part is handled by Picasso
return uri;
}
#Override
protected void onPostExecute(Uri uri) {
// You have to pass imageview in constructor.
// load image into ImageView
Picasso.with(mContext).load(uri)/* And load it here*/.into(mImageView);
}
}
This question was asked here: Universal-Image-Loader: wrong Bitmaps are attached to ImageView
I am using the the latest, 1.9.3. I implemented the solution by having this in my application class:
#Override
public void onCreate() {
super.onCreate();
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder().resetViewBeforeLoading(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.defaultDisplayImageOptions(defaultOptions)
.build();
ImageLoader.getInstance().init(config);
}
Per Android-Universal-Image-Loader doesn't keep loaded images on scroll in gridview, I load the image like so in my adapter:
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage(imgUrl, imageAware);
It still doesn't work; I'm wondering if it's because I have to make a call to get the image url since the api that supplied the model data did not include an image url.
So in my adapter's getView(), before I use imageloader with the image url, I do another asynch call to get the image url, like so:
APIclient.getImageJson(getContext(), googleUrl, new JsonHttpResponseHandler() {
#Override
public void onSuccess(JSONObject imgJson) {
try {
JSONObject responseDataValue = imgJson.getJSONObject("responseData");
JSONArray resultsValue = responseDataValue.getJSONArray("results");
JSONObject result = resultsValue.getJSONObject(0);
String imgUrl = result.getString("url");
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage(imgUrl, imageAware);
//ImageLoader.getInstance().displayImage(imgUrl, viewHolder.profileIV);
}
catch(Exception e) {
e.printStackTrace();
}
}
google url here looks like: https://ajax.googleapis.com/ajax/services/search/images?rsz=1&start=1&v=1.0&q=%22barack%20obama%22
each row would have a different url since the names are different. I don't know if the problem is still a listview recycer problem that wasn't fixed in the universal image loader library or if the culprit lies in the additional network call. How can I make the thumbnails consistent with the data next to it?
I think it's because of async call APIclient.getImageJson(...) in getView(). It's unknown time when onSuccess() callback is fired so you can call ImageLoader.getInstance().displayImage(...) for already recycled ImageView.
I can propose you join these 2 async operations (get JSON by APIclient, load image by ImageLoader) following way. Implement own ImageDoanloader which will process google JSON URLs (https://ajax.googleapis.com/ajax/services/search/images?rsz=1&start=1&v=1.0&q=%22barack%20obama%22), extract image URL and load image.
Let's introduce our own URI scheme - "json". So we know that incoming URIs like "json://..." are correspond to JSON link.
Prepare own downloader:
public class JsonImageDownloader extends BaseImageDownloader {
public static final String SCHEME_JSON = "json";
public static final String SCHEME_JSON_PREFIX = SCHEME_JSON + "://";
public JsonImageDownloader(Context context) {
super(context);
}
public JsonImageDownloader(Context context, int connectTimeout, int readTimeout) {
super(context, connectTimeout, readTimeout);
}
#Override
public InputStream getStream(String uri, Object extra) throws IOException {
if (uri.startsWith(SCHEME_JSON_PREFIX)) {
String jsonUri = uri.substring(SCHEME_JSON_PREFIX.length());
JSONObject imgJson = APIclient.getImageJson(context, jsonUri); // sync JSON loading
try {
JSONObject responseDataValue = imgJson.getJSONObject("responseData");
JSONArray resultsValue = responseDataValue.getJSONArray("results");
JSONObject result = resultsValue.getJSONObject(0);
String imgUrl = result.getString("url");
return super.getStream(imgUrl, extra);
} catch (JSONException e) {
throw new RuntimeException(e);
}
} else {
return super.getStream(uri, extra);
}
}
}
Set it into configuration
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.imageDownloader(new JsonImageDownloader(context))
....
.build();
Use ImageLoader in getView(...) without using APIclient:
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage("json://" + googleUrl, imageAware);
my app receive a jsonArray that have names of categories and their images urls.
so i want to extract the names and also download the image by they urls.
actually i succeed to extract the names and also the urls, look at the code bellow:
private class ReadPlacesFeedTask extends AsyncTask<String, Void, String> {
protected String doInBackground(String... urls) {
return readJSONFeed(urls[0]);
}
protected void onPostExecute(String result) {
try {
JSONObject jsonobject = new JSONObject(result);
JSONArray categories = new JSONArray(
jsonobject.getString("categories"));
for (int i = 0; i < categories.length(); i++) {
JSONObject category = categories.getJSONObject(i);
names.add(category.getString("name"));
images.add(category.getString("image"));
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getBaseContext(), R.layout.row_channel_layout,
R.id.channel_name, names);
channel_list = (ListView) findViewById(R.id.channel_list);
channel_list.setAdapter(adapter);
}
}
my questions:
1. is it ok to build the adapter in the onPostExecute method ?
2.should i write another a synctasck class to download the images?,or extract the urls in doInbackground method and after that download the images, and return both of the images and the names to onPostExecute method.
actually i try this and i think it is very slow because decoding the json in the background method is very slow !
3.how could i build the listView after downloading the names, then after downloading the images, i just modify it.
thank you
1: Typically yes, though your better to make the adapter then pass the values to it on the post execute then call adapter.NotifyDataSetChanged();
2: I recommend not reinventing the wheel and using the "fantastic" picasso image loading library
http://square.github.io/picasso/
3: Create one listview, pass the names and urls to it, make the array adapter into your own custom e.g. "myListAdapter" which then sets the name value and then using picasso download and load the image into your imageview or whatever else you want to do with it.
http://www.vogella.com/tutorials/AndroidListView/article.html#adapterown_custom
E.g.
public class MainActivity extends Activity
{
ArrayList<String> myListItems = new ArrayList<String>();
ArrayAdapter<String> adapter;
#Override
protected void onCreate(Bundle savedInstanceState)
{
adapter = new ArrayAdapter(this,R.layout.row_category_layout,R.id.category_name, names);
//execute my data task
}
public void dataTask()
{
//GOT SOME DATA
myListItems.add("MYDATA");
adapter.notifyDataSetChanged();
}