
Так как у 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.