Просмотр своих и чужих фотографий в Instagram под Android

Просмотр своих и чужих фотографий в Instagram под Android

Так как у Instagram нету родного API под Android платформу для авторизации и просмотра чужих и своих фотографий, то в этой статье мы реализуем 5 классов, с помощью которых сможем авторизовать пользователя и отобразить список его фоток в GridView.

Для начала нам нужен обычный аккаунт в Instagram из которого мы сделаем аккаунт для разработчиков тут. Дальше нам нужно выбрать Register new Client ID и заполнить все поля. После этого у нас будет Application в Sandbox режиме. Это означает, что мы можем пригласить еще 9 человек (смотри вкладку Sandbox в Manage Client) и тестировать возможности Instagram REST API между ними. После этого можно можно пройти рецензию и получить нужные права для того что-бы все другие пользователи могли использовать Instagram REST API а не только 9 человек из Sandbox.

Для общения с Instagram REST API будет использоваться OKHttp, а для загрузки фоток будет использоваться Glide.

Дальше создаем пакет instagram в корне нашего проекта и начинаем наполнять его вспомогательными классами. Первым из них будет класс Instagram, его основная функция - инициализация Instagram клиента.

// file instagram/Instagram.java
public class Instagram {
    public static final String AUTH_URL = "https://instagram.com/oauth/authorize/?";
    public static final String ACCESS_TOKEN_URL = "https://api.instagram.com/oauth/access_token";
    public static final String API_BASE_URL = "https://api.instagram.com/v1";

    private Context context;

    private InstagramDialog dlg;
    private InstagramAuthListener listener;
    private InstagramSession session;

    private String clientId;
    private String clientSecret;
    private String redirectUri;

    public Instagram(Context context, String clientId, String clientSecret, String redirectUri) {
        this.context = context;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri;

        String authUrl = AUTH_URL + "client_id=" + clientId + "&redirect_uri=" + redirectUri + "&response_type=code&scope=basic+public_content";

        session = new InstagramSession(context);
        dlg = new InstagramDialog(context, authUrl, redirectUri, new InstagramDialog.InstagramDialogListener() {
            @Override
            public void onSuccess(String code) {
                retrieveAccessToken(code);
            }

            @Override
            public void onError(String error) {
                listener.onError(error);
            }

            @Override
            public void onCancel() {
                listener.onCancel();
            }
        });
    }

    public void authorize(InstagramAuthListener listener) {
        this.listener = listener;
        dlg.show();
    }

    public void resetSession() {
        session.reset();
        dlg.clearCache();
    }

    public InstagramSession getSession() {
        return session;
    }

    private void retrieveAccessToken(String code) {
        new AccessTokenTask(code).execute();
    }

    public class AccessTokenTask extends AsyncTask<URL, Integer, Long> {        
        ProgressDialog progressDlg;
        InstagramUser user;
        String code;

        public AccessTokenTask(String code) {
            this.code = code;
            progressDlg = new ProgressDialog(context);
            progressDlg.setMessage("Getting access token...");          
        }

        protected void onCancelled() {
            progressDlg.cancel();
        }

        protected void onPreExecute() {
            progressDlg.show();
        }

        protected Long doInBackground(URL... urls) {         
            long result = 0;

            try {
                HashMap<String, String> params = new HashMap<String, String>(5);

                params.put("client_id", clientId);
                params.put("client_secret", clientSecret);
                params.put("grant_type", "authorization_code");
                params.put("redirect_uri", redirectUri);
                params.put("code", code);

                InstagramRequest request = new InstagramRequest();
                String response = request.post(ACCESS_TOKEN_URL, params);

                if (!response.equals("")) {
                    JSONObject jsonObj = (JSONObject) new JSONTokener(response).nextValue();
                    JSONObject jsonUser = jsonObj.getJSONObject("user");

                    user = new InstagramUser();
                    user.accessToken = jsonObj.getString("access_token");
                    user.id = jsonUser.getString("id");
                    user.username = jsonUser.getString("username");
                    user.fullName = jsonUser.getString("full_name");
                    user.profilePicture = jsonUser.getString("profile_picture");
                }                       
            } catch (Exception e) { 
                e.printStackTrace();
            }

            return result;
        }

        protected void onProgressUpdate(Integer... progress) {}

        protected void onPostExecute(Long result) {         
            progressDlg.dismiss();

            if (user != null) {
                session.store(user);
                listener.onSuccess(user);
            } else {
                listener.onError("Failed to get access token");
            }
        }                
    }

    public interface InstagramAuthListener {
        public abstract void onSuccess(InstagramUser user);
        public abstract void onError(String error);
        public abstract void onCancel();
    }

    public ArrayList<String> getUserMedia(String token, int count, String userID) {
        ArrayList<String> photoList = new ArrayList<String>();

        try {
            HashMap<String, String> params = new HashMap<String, String>(1);
            params.put("count", String.valueOf(count));

            InstagramRequest request = new InstagramRequest(token);
            String response = request.createRequest("GET", "/users/" + userID + "/media/recent", params);

            if (!response.equals("")) {
                JSONObject jsonObj = (JSONObject) new JSONTokener(response).nextValue();
                JSONArray jsonData= jsonObj.getJSONArray("data");

                int length = jsonData.length();

                if (length > 0) {
                    photoList = new ArrayList<String>();

                    for (int i = 0; i < length; i++) {
                        JSONObject jsonPhoto = jsonData.getJSONObject(i).getJSONObject("images").getJSONObject("low_resolution");
                        photoList.add(jsonPhoto.getString("url"));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return photoList;
    }
}

Следующий по списку класс для инициализации WebView с помощью которого будет происходить авторизация пользователя в Instagram.

// file instagram/InstagramDialog.java
@SuppressLint({ "NewApi", "SetJavaScriptEnabled" })
public class InstagramDialog extends Dialog {

    private ProgressDialog spinner;
    private WebView webView;
    private LinearLayout content;
    private TextView tvTitle;

    private String authUrl;
    private String redirectUri;

    private InstagramDialogListener listener;

    static final FrameLayout.LayoutParams FILL = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);

    static final int MARGIN = 8;
    static final int PADDING = 2;

    static final String TAG = "DBG";

    public InstagramDialog(Context context, String authUrl, String redirectUri, InstagramDialogListener listener) {
        super(context);
        this.authUrl = authUrl;
        this.listener = listener;
        this.redirectUri = redirectUri;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        spinner = new ProgressDialog(getContext());
        spinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
        spinner.setMessage("Loading...");

        content = new LinearLayout(getContext());
        content.setOrientation(LinearLayout.VERTICAL);

        setUpTitle();
        setUpWebView();

        Display display = getWindow().getWindowManager().getDefaultDisplay();
        Point outSize = new Point();

        int width = 0;
        int height = 0;

        double[] dimensions = new double[2];

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            display.getSize(outSize);

            width = outSize.x;
            height = outSize.y;
        } else {
            width = display.getWidth();
            height = display.getHeight();
        }

        if (width < height) {
            dimensions[0] = 0.87 * width;
            dimensions[1] = 0.82 * height;
        } else {
            dimensions[0] = 0.75 * width;
            dimensions[1] = 0.75 * height;
        }

        addContentView(content, new FrameLayout.LayoutParams((int) dimensions[0], (int) dimensions[1]));
    }

    private void setUpTitle() {
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        //Drawable icon = getContext().getResources().getDrawable(R.drawable.icon);

        tvTitle = new TextView(getContext());

        tvTitle.setText("Instagram");
        tvTitle.setTextColor(Color.WHITE);
        tvTitle.setTypeface(Typeface.DEFAULT_BOLD);
        tvTitle.setBackgroundColor(0xFF163753);
        tvTitle.setPadding(MARGIN + PADDING, MARGIN, MARGIN, MARGIN);
        tvTitle.setCompoundDrawablePadding(MARGIN + PADDING);
        //title.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);

        content.addView(tvTitle);
    }

    private void setUpWebView() {
        webView = new WebView(getContext());

        webView.setVerticalScrollBarEnabled(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setWebViewClient(new InstagramWebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl(authUrl);
        webView.setLayoutParams(FILL);
        //webView.setLayoutParams(FILL);

        WebSettings webSettings = webView.getSettings();

        webSettings.setSavePassword(true);
        webSettings.setSaveFormData(false);

        content.addView(webView);
    }

    public void clearCache() {
        webView.clearCache(true);
        webView.clearHistory();
        webView.clearFormData();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        clearCache();
        listener.onCancel();
    }

    private class InstagramWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.d(TAG, "Redirecting URL " + url);

            if (url.startsWith(redirectUri)) {
                if (url.contains("code")) {
                    String temp[] = url.split("=");
                    listener.onSuccess(temp[1]);
                } else if (url.contains("error")) {
                    String temp[] = url.split("=");
                    listener.onError(temp[temp.length-1]);
                }

                InstagramDialog.this.dismiss();
                return true;
            }

            return false;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {   
            super.onReceivedError(view, errorCode, description, failingUrl);
            listener.onError(description);
            InstagramDialog.this.dismiss();
            Log.d(TAG, "Page error: " + description);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            spinner.show();
            Log.d(TAG, "Loading URL: " + url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

            String title = webView.getTitle();
            if (title != null && title.length() > 0) {
                tvTitle.setText(title);
            }
            spinner.dismiss();
        }
    }

    public interface InstagramDialogListener {
        public abstract void onSuccess(String code);
        public abstract void onCancel();
        public abstract void onError(String error);
    }
}

Класс InstagramRequest используется для выполнения GET и POST запросов к Instagram REST API. Этот класс использует OkHttp для общения с Instagram REST API. Как настроить и использовать OkHttp читай в How to do GET and POST requests in Android using OkHttp .

Для сохранения токена после авторизации и других данных о пользователе создадим класс InstagramSession.

// file instagram/InstagramSession.java
public class InstagramSession {
    private Context context;
    private SharedPreferences sp;

    private static final String SHARED = "Instagram_Preferences";
    private static final String USERID  = "userid";
    private static final String USERNAME = "username";
    private static final String FULLNAME = "fullname";
    private static final String PROFILEPIC = "profilepic";
    private static final String ACCESS_TOKEN = "access_token";

    public InstagramSession(Context context) {
        this.context = context;
        this.sp = context.getSharedPreferences(SHARED, Context.MODE_PRIVATE);
    }

    public void store(InstagramUser user) {
        Editor editor = sp.edit();

        editor.putString(ACCESS_TOKEN, user.accessToken);
        editor.putString(USERID, user.id);
        editor.putString(USERNAME, user.username);
        editor.putString(FULLNAME, user.fullName);
        editor.putString(PROFILEPIC, user.profilePicture);

        editor.commit();
    }

    public void reset() {
        Editor editor = sp.edit();

        editor.putString(ACCESS_TOKEN, "");
        editor.putString(USERID, "");
        editor.putString(USERNAME, "");
        editor.putString(FULLNAME, "");
        editor.putString(PROFILEPIC, "");

        editor.commit();

        CookieSyncManager.createInstance(context);

        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.removeAllCookie();
    }

    public InstagramUser getUser() {
        if (sp.getString(ACCESS_TOKEN, "").equals("")) {
            return null;
        }

        InstagramUser user = new InstagramUser();

        user.id = sp.getString(USERID, "");
        user.username = sp.getString(USERNAME, "");
        user.fullName = sp.getString(FULLNAME, "");
        user.profilePicture = sp.getString(PROFILEPIC, "");
        user.accessToken = sp.getString(ACCESS_TOKEN, "");

        return user;
    }

    public String getAccessToken() {
        return sp.getString(ACCESS_TOKEN, "");
    }

    public boolean isActive() {
        return (sp.getString(ACCESS_TOKEN, "").equals("")) ? false : true;
    }
}

Класс InstagramUser будет хранить в себе информацию о авторизированном пользователе.

public class InstagramUser {
    public String id;
    public String username;
    public String fullName;
    public String profilePicture;
    public String accessToken;

    @Override
    public String toString() {
        return "InstagramUser{" +
                "id='" + id + '\'' +
                ", username='" + username + '\'' +
                ", fullName='" + fullName + '\'' +
                ", profilPicture='" + profilePicture + '\'' +
                '}';
    }
}

Создадим Activity, в которой соберем все вспомогательные классы в рабочую цепочку.

public class InstagramActivity extends AppCompatActivity {
    private InstagramSession instagramSession;
    private Instagram instagram;

    private ProgressBar pbLading;
    private GridView gvPhotos;
    private LinearLayout llInstagramOff;
    private RelativeLayout rlInstagramOn;
    private Button btnLogout, btnLogin;

    private static final String CLIENT_ID = "XXX";
    private static final String CLIENT_SECRET = "XXX";
    private static final String REDIRECT_URI = "XXX";
    String TAG = "VVV";

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

        llInstagramOff =  (LinearLayout) findViewById(R.id.llInstagramOff);
        rlInstagramOn =  (RelativeLayout) findViewById(R.id.rlInstagramOn);
        btnLogin =  (Button) findViewById(R.id.btnLogin);
        btnLogout =  (Button) findViewById(R.id.btnLogout);

        btnLogout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                instagramSession.reset();
                toggleInstagram();
            }
        });

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                instagram.authorize(mAuthListener);
            }
        });

        instagram = new Instagram(this, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
        instagramSession = instagram.getSession();
        //toggleInstagram();

        loadInstagram();
    }


    private Instagram.InstagramAuthListener mAuthListener = new Instagram.InstagramAuthListener() {
        @Override
        public void onSuccess(InstagramUser user) {
            loadInstagram();
        }

        @Override
        public void onError(String error) {
            Toast.makeText(InstagramActivity.this, error, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCancel() {
            Toast.makeText(InstagramActivity.this, "OK. Maybe later?", Toast.LENGTH_SHORT).show();
        }
    };


    private void loadInstagram() {
        if (instagramSession.isActive()) {
            InstagramUser instagramUser = instagramSession.getUser();
            Log.d(TAG, "loadInstagram: " + instagramUser);

            pbLading = (ProgressBar) findViewById(R.id.pbLading);
            gvPhotos = (GridView) findViewById(R.id.gvPhotos);

            ((TextView) findViewById(R.id.tv_name)).setText(instagramUser.fullName);
            ((TextView) findViewById(R.id.tv_username)).setText(instagramUser.username);

            ImageView userIv = (ImageView) findViewById(R.id.iv_user);
            Glide.with(this).load(instagramUser.profilePicture).into(userIv);
            //Log.d(TAG, "instagramUser.profilPicture: " + instagramUser.profilPicture);

            new DownloadTask().execute();
        }
        toggleInstagram();
    }


    private void toggleInstagram() {
        if (instagramSession.isActive()) {
            llInstagramOff.setVisibility(View.GONE);
            rlInstagramOn.setVisibility(View.VISIBLE);
        } else {
            llInstagramOff.setVisibility(View.VISIBLE);
            rlInstagramOn.setVisibility(View.GONE);
        }
    }


    public class DownloadTask extends AsyncTask<URL, Integer, Long> {
        ArrayList<String> photoList;

        protected void onCancelled() {}

        protected void onPreExecute() {}

        protected Long doInBackground(URL... urls) {
            long result = 0;
            //String userId = instagramSession.getUser().id;
            String userId = "6000222747";
            photoList = instagram.getUserMedia(instagramSession.getAccessToken(), 10, userId);
            return result;
        }

        protected void onProgressUpdate(Integer... progress) {
        }

        protected void onPostExecute(Long result) {
            pbLading.setVisibility(View.GONE);

            if (photoList == null) {
                Toast.makeText(getApplicationContext(), "No photos available", Toast.LENGTH_LONG).show();
            } else {
                Log.d(TAG, "onPostExecute: " + photoList);

                DisplayMetrics dm = new DisplayMetrics();
                getWindowManager().getDefaultDisplay().getMetrics(dm);

                int width   = (int) Math.ceil((double) dm.widthPixels / 3);
                width = width - 20;
                int height  = width;

                PhotoListAdapter adapter = new PhotoListAdapter(InstagramActivity.this);
                adapter.setData(photoList);
                adapter.setLayoutParam(width, height);
                gvPhotos.setAdapter(adapter);
            }
        }
    }

}

Layout для этой активити такой.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <!-- OFF -->

    <LinearLayout
        android:id="@+id/llInstagramOff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"
        android:gravity="center">

        <Button
            android:id="@+id/btnLogin"
            android:layout_width="175dp"
            android:layout_height="wrap_content"
            android:text="Log in" />
    </LinearLayout>

    <!-- ON -->

    <RelativeLayout
        android:id="@+id/rlInstagramOn"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        android:padding="5dp">

        <ImageView
            android:id="@+id/iv_user"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:contentDescription="@string/app_name"
            android:src="@drawable/c19" />

        <LinearLayout
            android:layout_toRightOf="@+id/iv_user"
            android:layout_alignTop="@+id/iv_user"
            android:layout_alignBottom="@+id/iv_user"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:orientation="vertical"
            android:gravity="center_vertical">

            <TextView
                android:id="@+id/tv_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="#000000"
                android:textStyle="bold"
                android:textSize="17sp"
                android:text="Lorensius Londa"/>

            <TextView
                android:id="@+id/tv_username"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="lorensiuswlt"/>

        </LinearLayout>

        <Button
            android:id="@+id/btnLogout"
            android:layout_below="@+id/iv_user"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="Log out" />

        <RelativeLayout
            android:layout_below="@+id/btnLogout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp" >

            <GridView
                android:id="@+id/gvPhotos"
                android:verticalSpacing="5dp"
                android:horizontalSpacing="5dp"
                android:padding="5dp"
                android:stretchMode="columnWidth"
                android:numColumns="3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

            <ProgressBar
                style="?android:attr/progressBarStyleLarge"
                android:id="@+id/pbLading"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:layout_centerInParent="true"/>

        </RelativeLayout>

    </RelativeLayout>

</LinearLayout>

Вспомогательный класс PhotoListAdapter для адаптера для GridView.

public class PhotoListAdapter extends BaseAdapter {
    private Context context;
    private ArrayList<String> photos;
    private int width;
    private int height;

    public PhotoListAdapter(Context context) {
        this.context = context;
    }

    public void setData(ArrayList<String> data) {
        photos = data;
    }

    public void setLayoutParam(int width, int height) {
        this.width  = width;
        this.height = height;
    }

    @Override
    public int getCount() {
        return (photos == null) ? 0 : photos.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageIv;

        if (convertView == null) {
            imageIv = new ImageView(context);

            imageIv.setLayoutParams(new GridView.LayoutParams(width, height));
            imageIv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            imageIv.setPadding(0, 0, 0, 0);
        } else {
            imageIv = (ImageView) convertView;
        }

        Glide.with(context).load(photos.get(position)).into(imageIv);

        return imageIv;
    }
}

Результат

android_instagram_example.png

blog comments powered by Disqus