بدون شک، اپلیکیشنها و بازیهای زیادی را دیدهاید که با استفاده از اکانت گوگل پلاس، در آنها به طور مستقیم و بدون ثبت نام وارد میشویم! اما حالا، روش پیاده سازی موارد پایهای این قابلیت را یاد خواهیم گرفت تا بتوانیم در اپلیکیشنهای خودمان نیز از آن استفاده کنیم.
1. فعال کردن Sign-In برای برنامه
قبل از شروع به کدنویسی قابلیتهای اپلیکیشن اندروید، ابتدا نیاز داریم تا API قابلیت Sign-In را برای اپلیکیشن خود روشن کنیم؛ که از بخش Google Developer’s console سایت گوگل انجام میگیرد! در ادامه این کار را انجام میدهیم.
ساخت یک پروژهی جدید
- وارد لینک «https://console.developers.google.com/project» شوید.
- روی CREATE PROJECT کلیک کنید.
- در بخش Project name، یک نام برای پروژهی خود انتخاب کنید. برای مثال: FuLLKade-SignIn
- روی CREATE کلیک کنید.
– کمی صبر کنید تا پیام اتمام عملیات ایجاد پروژه برایتان ظاهر شود.
– سپس صفحه را ریستارت کنید تا پروژهی خود را در لیست مشاهده نمایید.
– همانطور که میبینید، یک یک project id نیز با توجه به نام پروژهی شما برای آن در نظر گرفته شده است که منحصر به فرد است!
فعال کردن Google+ API
- روی پروژه کلیک کنید تا وارد صفحهی اختصاصی آن شوید.
- از بخش سرچ، عبارت API را جستجو کرده و سپس روی APIs & Services کلیک کنید.
- روی ENABLE APIS AND SERVICE کلیک کنید.
- به دنبال Google+ API بگردید و روی آن کلیک کنید.
- روی ENABLE کلیک کنید تا برای پروژهی شما فعال شود. (کمی صبر کنید تا صفحه بارگزاری شود.)
- همان مرحلهی دوم را تکرار کنید تا به صفحهی Dashboard برگردید.
- در انتهای صفحهی باز شده، جدولی را مشاهده میکنید که Google+ API نیز در آن قرار دارد؛ روی آن کلیک کنید.
اینجا صفحهی پلاگین Google+ API مربوط به پروژهی شماست.
پیکربندی مجوزها
گوگل میخواهد مطمئن شود که اپلیکیشن شما، یک اپلیکیشن فیک (جعلی) نیست؛ و برای همین، شما به SHA-1 کلید (keystore) پروژهی اندروید خود نیاز خواهید داشت! که البته برای هدف توسعه، از مجوز دیباگ میتوانید استفاده کنید و زمانی که قصد انتشار دارید، از یک مجوز واقعی keystore استفاده نمایید.
برای دسترسی به SHA-1 کلید دیباگ:
- در مک و لینوکس، به مسیر ~/.android بروید.
- در ویندوز، به مسیر زیر بروید:
C:\Users\<your user name>\.android.C:\Users\<your user name>\.android
توجه داشته باشید که .android، یک پوشهی مخفی است.
فایل debug.keystore شامل SHA-1 کلید دیباگ است. برای بدست آوردن آن، وارد CMD شده و دستور زیر را اجرا کنید:
keytool -list -v -keystore "C:/Users/Hadi/.android/debug.keystore" -alias androiddebugkey -storepass android -keypass android
توجه داشته باشید که ابتدا بایستی مسیر فایل debug.keystore خودتان را در دستور بالا جایگزاری کرده و سپس آن اجرا کنید. پس از اجرای آن، متنی چاپ خواهد شد. که باید به دنبال Certificate fingerprints گشته و آن را پیدا کنید؛ سپس در زیر آن، SHA1 و حتی SHA256 را مشاهده میکنید. مقدار مقابل SHA1 را کپی کرده و در جایی نگهدارید تا در مرحلهی بعدی از آن استفاده کنیم. مثلا برای من به صورت زیر است:
پیکربندی Consent Screen
- در همان صفحهی APIs & Services، روی Credentials کلیک کنید.
- وارد سربرگ Consent Screen شوید.
- اطلاعات موجود در صفحهی باز شده را تکمیل کنید.
در واقع اطلاعات اپلیکیشن و محصول خودتان را در اینجا تکمیل میکنید.
نام اپلیکیشن (Application name) و ایمیل پشتیبانی (Support email) ضروری بوده و بقیه اختیاری هستند. - در نهایت روی Save کلیک کنید.
پیکربندی Credentials
- در همان صفحه، به سربرگ Credentials برگردید.
- روی Create credentials کلیک کنید.
- روی OAuth Client ID کلیک کنید.
- نوع اپلیکیشن (Application type) خود را مشخص کنید که طبیعتا در اینجا باید Android باشد. یپی گزینههای زبر ظاهر خواهد شد:
- Name: یک نام برای OAuth client ID (این نام، نام نمایشی اپلیکیشن نیست.)
- Signing-certificate fingerprint: همان SHA-1 که در بالا کپی کردیم.
- Package name: پکیج اپلیکیشن
- روی Create کلیک کنید.
- صبر کنید تا پنحرهی OAuth client نمایش داده شود؛ سپس مقدار Client ID نمایش داده میشود.
- روی OK کلیک کنید.
- در لیستی که ظاهر خواهد شد، Client ID خود را مشاهده میکنید.
2. ایجاد اپلیکیشن اندرویدی
در این بخش، با مباحث پایهی ایجاد اپلیکیشنی با قابلیت Sign in گوگل آشنا میشویم.
ایجاد اپلیکیشن جدید
یک اپلیکیشن اندرویدی ایجاد کنید. پکیج باید همان پکیجی باشد که در مراحل قبلی و داخل سایت گوگل مشخص کردید! برای من به صورت زیر است:
- عنوان پروژه: FuLLKadeSignIn
- پکیج: com.fullkade.signin
پیکربندی build.gradle ماژول اپلیکیشن
موارد زیر را به Dependencies های پروژهی خود اضافه کنید:
implementation 'com.google.android.gms:play-services:7.3.0'
پیکربندی AndroidManifest.xml
کد زیر را به قبل از بسته شدن تگ Application اضافه کنید:
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
دسترسیهای زیر را اضافه کنید:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" />
ایجاد یک UI ساده
فایل 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" tools:context=".MainActivity"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:padding="2dip" tools:ignore="MissingConstraints"> <com.google.android.gms.common.SignInButton android:id="@+id/sign_in_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:enabled="false" /> <Button android:id="@+id/sign_out_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sign Out" android:enabled="true" /> <Button android:id="@+id/revoke_access_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Revoke Access" android:enabled="true" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/statuslabel" android:text="Status"/> </LinearLayout> </LinearLayout>
3. کدنویسی اپلیکیشن
در اینجا بیایید به همراه مثالی، به نحوهی استفاده از این قابلیت در اپلیکیشن خود بپردازیم. بنابراین برای شروع، ابتدا به کد کامل اکتیویتی دقت کرده و سپس مرحله به مرحله آن را شرح میدهم:
package com.fullkade.signin; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import android.app.PendingIntent; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.SignInButton; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.common.api.Scope; import com.google.android.gms.plus.Plus; public class MainActivity extends AppCompatActivity implements ConnectionCallbacks, OnConnectionFailedListener, View.OnClickListener { private static final int SIGNED_IN = 0; private static final int STATE_SIGNING_IN = 1; private static final int STATE_IN_PROGRESS = 2; private static final int RC_SIGN_IN = 0; private GoogleApiClient mGoogleApiClient; private int mSignInProgress; private PendingIntent mSignInIntent; private SignInButton mSignInButton; private Button mSignOutButton; private Button mRevokeButton; private TextView mStatus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSignInButton = (SignInButton) findViewById(R.id.sign_in_button); mSignOutButton = (Button) findViewById(R.id.sign_out_button); mRevokeButton = (Button) findViewById(R.id.revoke_access_button); mStatus = (TextView) findViewById(R.id.statuslabel); mSignInButton.setOnClickListener(this); mSignOutButton.setOnClickListener(this); mRevokeButton.setOnClickListener(this); mGoogleApiClient = buildGoogleApiClient(); } @Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override protected void onStop() { super.onStop(); mGoogleApiClient.disconnect(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case RC_SIGN_IN: if (resultCode == RESULT_OK) { mSignInProgress = STATE_SIGNING_IN; } else { mSignInProgress = SIGNED_IN; } if (!mGoogleApiClient.isConnecting()) { mGoogleApiClient.connect(); } break; } } private GoogleApiClient buildGoogleApiClient() { return new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Plus.API, Plus.PlusOptions.builder().build()) .addScope(new Scope("email")) .build(); } private void resolveSignInError() { if (mSignInIntent != null) { try { mSignInProgress = STATE_IN_PROGRESS; startIntentSenderForResult(mSignInIntent.getIntentSender(), RC_SIGN_IN, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { mSignInProgress = STATE_SIGNING_IN; mGoogleApiClient.connect(); } } else { } } private void onSignedOut() { mSignInButton.setEnabled(true); mSignOutButton.setEnabled(false); mRevokeButton.setEnabled(false); mStatus.setText("Signed out"); } @Override public void onConnected(@Nullable Bundle bundle) { mSignInButton.setEnabled(false); mSignOutButton.setEnabled(true); mRevokeButton.setEnabled(true); mSignInProgress = SIGNED_IN; try { String emailAddress = Plus.AccountApi.getAccountName(mGoogleApiClient); mStatus.setText(String.format("Signed In to My App as %s", emailAddress)); } catch (Exception ex) { String exception = ex.getLocalizedMessage(); String exceptionString = ex.toString(); } } @Override public void onConnectionFailed(@NonNull ConnectionResult result) { if (mSignInProgress != STATE_IN_PROGRESS) { mSignInIntent = result.getResolution(); if (mSignInProgress == STATE_SIGNING_IN) { resolveSignInError(); } } onSignedOut(); } @Override public void onConnectionSuspended(int cause) { mGoogleApiClient.connect(); } @Override public void onClick(View v) { if (!mGoogleApiClient.isConnecting()) { switch (v.getId()) { case R.id.sign_in_button: mStatus.setText("Signing In"); resolveSignInError(); break; case R.id.sign_out_button: Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); mGoogleApiClient.disconnect(); mGoogleApiClient.connect(); break; case R.id.revoke_access_button: Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient); mGoogleApiClient = buildGoogleApiClient(); mGoogleApiClient.connect(); break; } } } }
مراحل
ابتدا به MainActivity رفته و موارد زیر را ایمپورت کنید:
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
اینترفیسهای زیر را برای اکتیویتی خود پیاده کنید:
public class MainActivity extends AppCompatActivity implements ConnectionCallbacks, OnConnectionFailedListener, View.OnClickListener {
این اینترفیسها، متدهایی با نامهای onConnected، onConnectionSuspended، onConnectionFailed و onClick را به اکتیویتی اضافه میکنند؛ قرار است در ادامه این متدها با توجه به رویدادهایی که رخ میدهند اجرا شوند.
پیادهسازی ثابتها و متغیرها
ابتدا ثابتهای زیر را به اکتیویتی اضافه کنید:
private static final int SIGNED_IN = 0; // وضعیت ورود کامل (وارد شده) private static final int STATE_SIGNING_IN = 1; // وضعیت در حال ورود private static final int STATE_IN_PROGRESS = 2; // وضعیت در حال پردازش ورود private static final int RC_SIGN_IN = 0; // OnActivityResult
حالا سه متغیر زیر را تعریف کنید:
private GoogleApiClient mGoogleApiClient; private int mSignInProgress; // وضعیت ورود private PendingIntent mSignInIntent;
و در نهایت، کنترلهای UI را تعریف کنید:
private SignInButton mSignInButton; private Button mSignOutButton; private Button mRevokeButton; private TextView mStatus;
پیادهسازی onCreate اکتیویتی:
بیایید ببینیم در onCreate برای شروع چه باید نوشت؟! آن را به صورت زیر تکمیل کنید:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1 mSignInButton = (SignInButton) findViewById(R.id.sign_in_button); mSignOutButton = (Button) findViewById(R.id.sign_out_button); mRevokeButton = (Button) findViewById(R.id.revoke_access_button); mStatus = (TextView) findViewById(R.id.statuslabel); // 2 mSignInButton.setOnClickListener(this); mSignOutButton.setOnClickListener(this); mRevokeButton.setOnClickListener(this); //3 mGoogleApiClient = buildGoogleApiClient(); }
شرح کد:
- متغیرهای mSignInButton (دکمه ورود)، mSignOutButton (دکمه خروج)، mRevokeButton (دکمه ابطال کامل و خروج) و mStatus (وضعیت فعلی) را مقداردهی میکنیم.
- برای همهی دکمهها، رویداد کلیک اینترفیس View.OnClickListener که در اکتنیویتی پیاده شده است را تنظیم میکنیم.
- mGoogleApiClient را که برای برقراری ارتباط با سرویس Play گوگل است، توسط متد buildGoogleApiClient مقداردهی میکنیم.
متد buildGoogleApiClient:
این متد که خودمان آن را پیاده میکنیم، یک آبجکت (GoogleApiClient) جدیدی جهت اتصال به API سرویس گوگل، با تنظیمات دلخواهمان تولید کرده و برایمان برمیگرداند.
private GoogleApiClient buildGoogleApiClient() { return new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Plus.API, Plus.PlusOptions.builder().build()) .addScope(new Scope("email")) .build(); }
شرح GoogleApiClient:
- متد سازنده: یک Context دریافت میکند.
- متد addConnectionCallbacks: اینترفیس ConnectionCallbacks دریافت میکند؛ که در اینجا، اینترفیس پیادهشده روی اکتیویتی را در نظر گرفتهایم. و همچنین این اینترفیس، دو متد onConnected و onConnectionSuspended را شامل میشود:
onConnected: وقتی به سرویس گوگل متصل شود.
onConnectionSuspended: وقتی وضعیت انصال معلق شود. - متد addOnConnectionFailedListener: اینترفیس OnConnectionFailedListener دریافت میکند؛ که در اینجا، اینترفیس پیادهشده روی اکتیویتی را در نظر گرفتهایم. و همچنین این اینترفیس، تنها شامل متد onConnectionFailed میباشد؛ که در هنگام ناموفق بودن ارتباط، اجرا میشود.
- متد addApi: برای مشخص کردن API مورد نیاز به کار میرود؛ که در اینجا، گوگل پلاس مورد نیاز ماست!
- متد addScope: برای مشخص کردن اطلاعات درخواستی میباشد. مثلا در اینجا، تنها به ایمیل نیاز داریم؛ که میتوان بعدا به جنسیت و … نیز اشاره کرد.
پیادهسازی متدهای onStart() و onStop() خود اکتیویتی:
@Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override protected void onStop() { super.onStop(); mGoogleApiClient.disconnect(); }
در هنگام شروع اکتیویتی، کلاینت را به سرویس گوگل متصل کرده و در هنگام توقف اکتیویتی، ارتباط را قطع میکنیم.
مدیریت رویداد معلق شدن:
@Override public void onConnectionSuspended(int cause) { mGoogleApiClient.connect(); }
در صورت معلق شدن، دوباره سعی کن متصل شوی!
مدیریت رویداد onConnected:
این رویداد، قرار است که پس از کلیک روی دکمهی Sign in و در صورت موفق بودن اتصال، اجرا شود.
@Override public void onConnected(@Nullable Bundle bundle) { // 1 mSignInButton.setEnabled(false); mSignOutButton.setEnabled(true); mRevokeButton.setEnabled(true); // 2 mSignInProgress = SIGNED_IN; // 3 try { String emailAddress = Plus.AccountApi.getAccountName(mGoogleApiClient); mStatus.setText(String.format("Signed In to My App as %s", emailAddress)); } catch (Exception ex) { String exception = ex.getLocalizedMessage(); String exceptionString = ex.toString(); } }
شرح کد:
- زمانی که متصل شد، دکمهی Sign in غیرفعال شده و دو دکمهی دیگر فعال شوند.
- به کمک متغیر mSignInProgress و ثابتهایی که نوشتهایم، مشخص میکنیم که در حال حاظر، وصعبت sign in کاربر در مرحلهی ورود کامل (SIGNED_IN) قرار دارد.
- آدرس ایمیل را از پلاس دریافت کرده و سپس درون mStatus پیامی را نمایش میدهیم! همچنین ممکن است خطایی رخ دهده که آن را هم مدیریت کردهایم.
این مورد نیاز به مجوز GET_ACCOUNTS دارد؛ وگرنه خطا رخ میدهد.
مدیریت رویداد onConnectionFailed:
این رویداد، معمولا در صورت وجود مشکل در اتصال اجرا میشود.
@Override public void onConnectionFailed(@NonNull ConnectionResult result) { if (mSignInProgress != STATE_IN_PROGRESS) { mSignInIntent = result.getResolution(); if (mSignInProgress == STATE_SIGNING_IN) { resolveSignInError(); } } onSignedOut(); }
شرح کد:
اگر وضعیت ورود به حساب، در حال پردازش نبود (که این وضعیت را در ادامه خواهیم دید)، در این صورت، از ورودی ConnectionResult دریافت شده در داخل رویداد، PendingIntent را به دست آورده و سپس resolveSignInError را اجرا میکنیم (متدی برای حل ارور ورود به حساب که در ادامه مینویسیم)! و در نهایت، متد onSignedOut (رویدادی که قرار است موقع Sign Out شدن فراخوانی شود) را اجرا میکنیم. این متد یک متد شخصی است که در ادامه آن را پیاده خواهیم کرد. البته خود onConnectionFailed نیز حکم Sign Out را دارد؛ ولی چون بررسی دیگری نیز درون آن انجام دادهایم، متد دیگری برای رویداد Sign Out تعریف کردهایم تا منطق بهتری داشته باشد.
متد resolveSignInError:
private void resolveSignInError() { if (mSignInIntent != null) { try { mSignInProgress = STATE_IN_PROGRESS; startIntentSenderForResult(mSignInIntent.getIntentSender(), RC_SIGN_IN, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { mSignInProgress = STATE_SIGNING_IN; mGoogleApiClient.connect(); } } else { // You have a play services error -- inform the user } }
شرح کد:
اگر mSignInIntent مقداردهی شده باشد، یعنی در وضعیت sign in قرار داد و خطایی رخ داده است؛ پس ابتدا وضعیت ورود را به در حال پردازش تغییر داده و سپس یک دیالوگی به کاربر نمایش میدهیمکه اکانت مورد نظر را برای لاگین شدن انتخاب کند. و جواب آن در رویداد onActivityResult برگردانده میشود. request code را نیز برابر RC_SIGN_IN قرار دادهایم.
اما اگر mSignInIntent خالی بود، یعنی خطا از طرف خود سرویس گوگل است؛ که البته در کد بالا، برای آن پیادهسازی خاصی انجام ندادیم.
پیادهسازی onActivityResult:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case RC_SIGN_IN: if (resultCode == RESULT_OK) { mSignInProgress = STATE_SIGNING_IN; } else { mSignInProgress = SIGNED_IN; } if (!mGoogleApiClient.isConnecting()) { mGoogleApiClient.connect(); } break; } }
شرح کد:
اگر نتیجه (requestCode) برابر RC_SIGN_IN بود، و در صورتی که resultCode برابر RESULT_OK بود، حالت وضعیت فعلی را به STATE_SIGNING_IN (در حال اتصال) تغییر بده؛ در غیر این صورت، آن را به SIGNED_IN (متصل شده) تغییر بده.
و در ادامه بررسی میکنیم که اگر در حال اتصال به سروریس گوگل نبود، یک اتصال جدید صورت گیرد. دقت کنید که در حال اتصال را بررسی میکنیم نه این که آیا متصل شده است یا خیر!
پیادهسازی متد onSignedOut:
این متد، در زمانی که Sign Out رخ داده و از حساب خارح شده باشد، رخ میدهد. آن را در بالا و داخل onConnectionFailed فراخوانی کردیم.
private void onSignedOut() { mSignInButton.setEnabled(true); mSignOutButton.setEnabled(false); mRevokeButton.setEnabled(false); mStatus.setText("Signed out"); }
پیاده سازی متد onClick:
حالا بیایید مشخص کنیم که وقتی روی دکمهها کلیک شد چه رخ دهد!
@Override public void onClick(View v) { if (!mGoogleApiClient.isConnecting()) { switch (v.getId()) { case R.id.sign_in_button: mStatus.setText("Signing In"); resolveSignInError(); break; case R.id.sign_out_button: Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); mGoogleApiClient.disconnect(); mGoogleApiClient.connect(); break; case R.id.revoke_access_button: Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient); mGoogleApiClient = buildGoogleApiClient(); mGoogleApiClient.connect(); break; } } }
شرح کد:
- اگر روی هرکدام از دکمههای ورود، خروج و معلق کلیک شد؛ ولی هنوز کلاینت به سرور گوگل متصل نشده بود، هیچ اتفاقی رخ ندهد.
- اگر روی ورود به حساب کلیک شد:
پیام “Signing In” (در حال ورود) را در mStatus به کاربر نمایش دهد.
متد resolveSignInError اجرا شود. (این متد در بالا توضیح داده شد)
در واقع در اولین اجرای برنامه، onConnectionFailed رخ خواهد داد و mSignInIntent نیز مقداردهی میشود؛ لذا با اجرای resolveSignInError، دیالوگ انتخاب حساب کاربری جهت اتصال به آن نمایش داده میشود. - اگر روی خروج از حساب کلیک شد:
کلاینت فعلی توسط Plus.AccountApi.clearDefaultAccount پاکسازی شود.
متد disconnect اجرا شود. - اگر روی ابطال کلیک شد: (ابطال کامل کلاینت ایجاد شده)
کلاینت فعلی توسط Plus.AccountApi.clearDefaultAccount پاکسازی شود.
دسترسی را ابطال کرده و از حساب خارج شود.
یک کلاینت جدید از ابتدا بسازد و به آن متصل شود.
برنامه آماده اجرا است! به همین سادگی.
سلام خسته نباشید
من فایل debug.keystore رو نتونستم پیدا کنم. توی پوشه ای هم ک گفتین نبود