Так как у 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; } }
Результат
Информацию о пользователе в виде json можно получить добавив к url ?__a=1
, например, https://www.instagram.com/proftua/?__a=1.