I try to implement an account manager in my app to avoid that the user has to log in each time he opens the app.
So basically, I already have my Authentication Activity where the user can put its login and password and where we receive a token from the server (the authentication is basic for now). Now I want to add the AccountManager but I don't really understand which part would go where.
What I need is pretty basic:
add an account if I never logged in before
log automatically if my account exists
if the auto authentication doesn't work get a new token on the server
Here is my code :
AuthenticationActivity.java
public class AuthenticationActivity extends Activity {
private EditText editTextUsername;
private EditText editTextPassword;
private Button buttonLogin;
private ProgressBar spinner;
private TextView error;
private TextView register;
private boolean accountRegistred;
AccountManager accountManager;
public static final String AUTHENTICATION = "authentication"; //action
private ConnectionSuccessReceiver connectionSuccessReceiver = new ConnectionSuccessReceiver();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.authentification);
accountManager = AccountManager.get(this);
Account[] accounts = accountManager.getAccountsByType("login");
if (accounts.length > 0) {
//If there is an account
} else {
accountRegistred = false;
editTextUsername = (EditText) findViewById(R.id.editText_login);
editTextUsername.setVisibility(View.VISIBLE);
editTextPassword = (EditText) findViewById(R.id.editText_password);
editTextPassword.setVisibility(View.VISIBLE);
buttonLogin = (Button) findViewById(R.id.button_connection);
buttonLogin.setVisibility(View.VISIBLE);
error = (TextView) findViewById(R.id.textView_error);
register = (TextView) findViewById(R.id.textView_register);
register.setVisibility(View.VISIBLE);
spinner = (ProgressBar) findViewById(R.id.progressBar);
buttonLogin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Here we start the service which will reach the server
Intent i = new Intent(getApplicationContext(), AuthenticationService.class);
i.putExtra("username", editTextUsername.getText().toString());
i.putExtra("password", editTextPassword.getText().toString());
getApplication().startService(i);
spinner.setVisibility(View.VISIBLE);
error.setVisibility(View.INVISIBLE);
}
});
register.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startActivity(new Intent(AuthenticationActivity.this, RegisterActivity.class));
}
});
}
registerReceiver(connectionSuccessReceiver, new IntentFilter(AUTHENTICATION));
}
private class ConnectionSuccessReceiver extends BroadcastReceiver {
//Called when the server returns success after authentication, we get the TOKEN here
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getStringExtra("STATE").equals("CONNECTED")) {
Intent i = new Intent(AuthenticationActivity.this, MainActivity.class);
i.putExtra("TOKEN", intent.getStringExtra("TOKEN"));
startActivity(i);
} else {
spinner.setVisibility(View.INVISIBLE);
error.setVisibility(View.VISIBLE);
}
finish();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(connectionSuccessReceiver);
}
}
AuthenticatorService.java
public class AuthenticatorService extends Service {
/**
* The implementation of the class |AccountAuthenticatorImpl|.
* It is implemented as a singleton
*/
private static AccountAuthenticator accountAuthenticator = null;
/**
* The main constructor.
*/
public AuthenticatorService() {
super();
}
/**
* The bind method of the service.
* #param intent The intent used to invoke the service
* #return The binder of the class which has implemented |AbstractAccountAuthenticator|
*/
#Override
public IBinder onBind(Intent intent) {
IBinder ret = null;
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
ret = getAuthenticator().getIBinder();
}
return ret;
}
/**
* The method used to obtain the authenticator. It is implemented as a singleton
* #return The implementation of the class |AbstractAccountAuthenticator|
*/
private AccountAuthenticator getAuthenticator() {
if (AuthenticatorService.accountAuthenticator == null) {
AuthenticatorService.accountAuthenticator = new AccountAuthenticator(this);
}
return AuthenticatorService.accountAuthenticator;
}
public class AccountAuthenticator extends AbstractAccountAuthenticator {
private Context context;
public AccountAuthenticator(Context context) {
super(context);
this.context = context;
}
#Override
public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) {
return null;
}
#Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Bundle reply = new Bundle();
Intent i = new Intent(context, AuthenticationActivity.class);
i.setAction("com.readyo.app.authentication.addnewaccount");
i.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
i.putExtra("AuthTokenType", authTokenType);
reply.putParcelable(AccountManager.KEY_INTENT, i);
return reply;
}
#Override
public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException {
return null;
}
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
return null;
}
#Override
public String getAuthTokenLabel(String s) {
return null;
}
#Override
public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
return null;
}
#Override
public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException {
return null;
}
}
}
I have also code to reach the server via HTTP but I'm not sure it would be important here.
Thank you for your time.
It's a bit late response but maybe this sample could help you:
https://github.com/dawidgdanski/AccountAuthenticatorExample
I created it some time ago but the logic with signing up/logging in may be helpful
add an account if I never logged in before:
If your app flow requires user to log in order to gain access to the data, then simply declare your LoginActivity as the primary one to be displayed.
Once you validate and verify user credentials, call AccountManager.addAccountExcplicitly() method.
On the other hand, if you expose some screens to be seen for anonymous users then in the application part (settings or whatever) where you provide login/sign up functionality call AccountManager.addAccount(). This call activates your AccountAuthenticator that processes your request in YourAccountAuthenticator.addAccount() and may display LoginActivity/SignUpActivity according to your needs.
Please, bear in mind that you may create app-specific account from System Settings as well.
log automatically if my account exists
Well, I'm not sure whether I understand your demand correctly. Once you store Account in AccountManager's meta data it is available once you call AccountManager.getAccountsByType("my.account.type").
If you want to log in automatically then you must somewhere store your credentials which is obviously under the threat of sensitive data leak.
if the auto authentication doesn't work get a new token on the server
There is an AccountManager.invalidateAuthToken() method that removes currently stored authToken and calls for another one.
You can launch the example app, I think it may solve at least some of your problems because it covers the following logic:
login/signup
auth token invalidation
displaying currently logged accounts
logout
Cheers
Related
I used the same process for sending data between fragments and it works but now I'm not getting data in Receiver Activity. Even the Log message Tag is not showing as I click on submit button. I checked in Sender Activity Log message and it is showing data but can't get those data in Receiver Activity.
Please help me to get data. Thank you!!
ViewModel Class:
public class ViewModelClass extends ViewModel {
private final MutableLiveData message = new MutableLiveData();
public void setMessage(HomeModelClass data){
message.setValue(data);
}
public MutableLiveData getMessage() {
return message;
}
}
Sender Activity:
public class EditHomeData extends AppCompatActivity {
private ViewModelClass viewModelClass;
HomeModelClass homeModelClassData = new HomeModelClass();
#Override
protected void onCreate(Bundle savedInsatancestate) {
super.onCreate(savedInsatancestate);
setContentView(R.layout.first_page);
viewModelClass = ViewModelProviders.of(this).get(ViewModelClass.class);
setValues();
});
public void setValues() {
if (yes.isChecked()) {
rent_value = String.valueOf(1);
} else if (no.isChecked()) {
rent_value = String.valueOf(0);
}
homeModelClassData.setWard_id(ward_id + "");
homeModelClassData.setToleName(tole_name.getText().toString());
homeModelClassData.setHouseAge(house_age.getText().toString());
homeModelClassData.setRadio(rent_value);
homeModelClassData.setTotal_tenant(editText1.getText().toString());
homeModelClassData.setMale_tenant(editText2.getText().toString());
homeModelClassData.setFemale_tenant(editText3.getText().toString());
homeModelClassData.setHouse_stroyes(spi1);
homeModelClassData.setRoof_types(spi2);
homeModelClassData.setLatitude(lati.getText().toString());
homeModelClassData.setLongitude(longi.getText().toString());
viewModelClass.setMessage(homeModelClassData);
}
Receiver Activity:
public class EditHomeData3 extends AppCompatActivity {
Button submit, cancel;
String ward_id, houseNumber, toleName, house_age, radio, total_tenant, male_tenant, female_tenant, house_stroyes,
roof_types, latitude, longitude, value_updateby;
#Override
protected void onCreate(Bundle savedInsatancestate) {
super.onCreate(savedInsatancestate);
setContentView(R.layout.third_page);
submit = findViewById(R.id.submit_btn);
submit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getDatafromField();
}
});
private void getDatafromField() {
final ViewModelClass model = ViewModelProviders.of(this).get(ViewModelClass.class);
model.getMessage().observe(this, new Observer() {
#Override
public void onChanged(#Nullable Object o) {
if (o instanceof HomeModelClass) {
HomeModelClass homedata = (HomeModelClass) o;
ward_id = homedata.getWard_id();
houseNumber = homedata.getHouseNumber();
toleName = homedata.getToleName();
house_age = homedata.getHouseAge();
radio = homedata.getRadio();
total_tenant = homedata.getTotal_tenant();
male_tenant = homedata.getMale_tenant();
female_tenant = homedata.getFemale_tenant();
house_stroyes = homedata.getHouse_stroyes();
roof_types = homedata.getRoof_types();
latitude = homedata.getLatitude();
longitude = homedata.getLongitude();
value_updateby = String.valueOf("1");
Log.i("GetMessage", houseNumber +"");
}
}
});
}
ViewModels are not shared across Activities - since you pass a different object to ViewModelProviders.of(), you'll get different ViewModel instances.
This was specifically called out in the Single Activity: Why, When, and How talk as a reason to prefer a single Activity architecture in your app.
Yes Indeed, ViewModel are not shared across activities,So either you create different viewmodel for different activies or you could use different fragment with same viewmodel. Because in fragment you can achieved using SharedViewModel
In the main activity, I have LiveData which contains members and a click listener. If I click on a member, then his ID is passed with intent.putExtra. That ID is later passed on to the method open in this activity. With this activity, I want to see the details of a member. In my MemberInfo activity, I marked a line where my problem lies.
It shows me this error: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
My DAO consists this code:
#Query("SELECT * FROM member_table WHERE MemberID=:id")
Member getMemberInfo(long id);
This is my main activity:
public class MemberMainActivity extends AppCompatActivity implements MemberListAdapter.MemberClickListener{
private MemberViewModel mMemberViewModel;
private List<Member> mMember;
void setMember(List<Member> members) {
mMember = members;
}
public static final int NEW_MEMBER_ACTIVITY_REQUEST_CODE = 1;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_member);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MemberMainActivity.this, NewMemberActivity.class);
startActivityForResult(intent, NEW_MEMBER_ACTIVITY_REQUEST_CODE);
}
});
RecyclerView recyclerView = findViewById(R.id.recyclerviewcard_member);
final MemberListAdapter adapter = new MemberListAdapter(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
mMemberViewModel = ViewModelProviders.of(this).get(MemberViewModel.class);
mMemberViewModel.getAllMember().observe(this, new Observer<List<Member>>() {
#Override
public void onChanged(#Nullable final List<Member> members) {
mMember = members;
// Update the cached copy of the words in the adapter.
adapter.setMember(members);
}
});
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == NEW_MEMBER_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
Member member = new Member(data.getStringExtra(NewMemberActivity.EXTRA_REPLY), data.getStringExtra(NewMemberActivity.EXTRA_REPLY2));
mMemberViewModel.insert(member);
} else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}
public void onMemberClick(int position) {
Member member = mMember.get(position);
Intent intent = new Intent(getApplicationContext(),MemberInfo.class);
intent.putExtra("MemberID", member.getId());
MemberInfo.open(this, member.getId());
}
}
This is my activity:
public class MemberInfo extends AppCompatActivity {
public static void open(Activity activity, long memberid) {
Intent intent = new Intent(activity, MemberInfo.class);
intent.putExtra("MemberID", memberid);
activity.startActivity(intent);
}
private List<Member> mMember;
private MemberViewModel mMemberViewModel;
void setMember(List<Member> members){
mMember = members;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memberinfo);
Log.i("okay", "memberinfo");
Intent intent = getIntent();
if (intent != null && intent.hasExtra("MemberID")) {
long memberid = intent.getLongExtra("MemberID", -1);
// TODO: get customer details based on customer id
TextView firstname = findViewById(R.id.layout_memberfirstname);
TextView surname = findViewById(R.id.layout_membersurname);
TextView balance = findViewById(R.id.layout_memberbalance);
-------------Member member = MemberRoomDatabase.getDatabase().memberDao().getMemberInfo(memberid);-------------
firstname.setText(member.getFirstname());
surname.setText(member.getSurname());
}
else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}
}
I thought that maybe it is because I'm missing a AsyncTask method. I tried this, but this also didn't work:
private static class insertMemberInfoAsyncTask extends AsyncTask<Member, Void, Void> {
private MemberDao mAsyncTaskDao;
insertMemberInfoAsyncTask(MemberDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(Member... params) {
Member member = params[0];
mAsyncTaskDao.getMemberInfo(member.getId());
return null;
}
}
public Member getMemberInfo(long id) {
mAllMember = mMemberDao.getAllMember();
Member member = mMemberDao.getMemberInfo(id);
new insertMemberInfoAsyncTask(mMemberDao).execute(member);
return member;
}
I think I use the method wrong. Can anybody help me?
One option is to update your query to this:
#Query("SELECT * FROM member_table WHERE MemberID=:id")
LiveData<Member> getMemberInfo(long id);
(or similar, using Flowable). This avoids the need to manually create your own AsyncTask.
Returning the LiveData wrapper around the Member type automatically signals to Room that the query can/should be performed asynchronously. Per https://developer.android.com/training/data-storage/room/accessing-data (my emphasis):
Note: Room doesn't support database access on the main thread unless you've called allowMainThreadQueries() on the builder because it might lock the UI for a long period of time. Asynchronous queries—queries that return instances of LiveData or Flowable—are exempt from this rule because they asynchronously run the query on a background thread when needed.
You can use Future and Callable. So you would not be required to write a long asynctask and can perform your queries without adding allowMainThreadQueries() or using LiveData.
My dao query:-
#Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();
My repository method:-
public UserData getDefaultData() throws ExecutionException, InterruptedException {
Callable<UserData> callable = new Callable<UserData>() {
#Override
public UserData call() throws Exception {
return userDao.getDefaultData();
}
};
Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);
return future.get();
}
In my case, it works if you add Dispatcher.IO when you use coroutines:
viewModelScope.launch(Dispatchers.IO) {
//your database call
}
For me allowMainThreadQueries() works.
This allows room to support database access on the main thread.
See the following code
#Database(entities = [Word::class ],version = 1)
abstract class VocabularyDatabase:RoomDatabase() {
companion object {
private lateinit var INSTANCE:VocabularyDatabase
fun getInstance(context:Context):VocabularyDatabase= Room.databaseBuilder(
context,
VocabularyDatabase::class.java,
"vocabulary"
)
.createFromAsset("vocabulary.db")
.allowMainThreadQueries()
.build()
}
abstract fun dao():WordDao
}
Using Future and Callables can be an alternative here. By using Future and Callable you can get rid of AsyncTask and forcing your queries to the main thread.
The syntax would be as follow -
#Throws(ExecutionException::class, InterruptedException::class)
private fun canContinue(id: String): UserData{
val callable = Callable { userDao.getDefaultData() }
val future = Executors.newSingleThreadExecutor().submit(callable)
return future!!.get()
}
And, don't forget the null check for the data returned. Because it might be null
I have 3 classes: LogIn, LogicController and WebService.
LogIn is an Activity that, by pressing a button, executes a static LogicController method which, in turn, executes a static method in WebService. WebService makes a request to the server using JsonObjectRequest. I need that interaction to represent the MVC model (a school work requires it), and I could not do it without static methods, since I could not "serialize" LogicController to pass as a variable to LogIn.
The problem is that I can not get LogIn to wait for WebService to complete its work before continuing, and I need that to use a parameter returned by it.
LogIn class:
public class LogIn extends AppCompatActivity {
EditText txtUsr;
EditText txtPass;
Button btnLogIn;
#Override
protected void onCreate(Bundle savedInstanceState) {
txtUsr = (EditText) findViewById(R.id.txtUsr);
txtPass = (EditText) findViewById(R.id.txtPass);
btnLogIn = (Button) findViewById(R.id.btnIS);
btnLogIn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
logInWebService();
}
});
}
private void logInWebService() {
String usr = txtUsr.getText().toString();
String pass = txtPass.getText().toString();
boolean result;
result = LogicController.logInWebService(this, usr, pass);
if(result){
//doSomething in response to the result
//need to execute this after logInWebService is done
}
}
}
LogicController class:
public class LogicController extends AppCompatActivity {
private WebService myWebService;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myWebService = new WebService(1);
}
public static boolean logInWebService(Context context, String usr, String pass){
boolean result = WebService.logInWebService(context, usr, pass);
return result;
}
}
WebService class:
public class WebService {
private static boolean result;
public WebService(int idGestor) {
this.idGestor = idGestor;
}
public static boolean logInWebService(Context context, final String usr, final String pass) {
String url = "https://webpage.myPhpWS.php?"+"idusr="+usr+"&pass="+pass;
result = false
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
if(someOperation){
result = true;
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
System.out.println("Error"+error.getMessage());
}
});
RequestQueue request = Volley.newRequestQueue(context);
request.add(jsonObjectRequest);
return result; //this is the final result needed
}
}
I use nested classes since the WebService class makes several requests, and I must be able to handle them separately. I would prefer, if possible, not to add classes to this scheme.
I could not find a thread that touched on this specific topic.
Any help would be useful, even if I'm having a bad approach from the beginning.
Thanks for advance.
you can use a callback interface and pass it as a parameter to the LogicController.logInWebService(this, usr, pass,callback);.
1- create interface ResultCallback
interface ResultCallback {
void on success();
void onFailure();
}
2- pass it to the logInWebService
//show progress dialog before making the request
LogicController.logInWebService(this, usr, pass,new ResultCallback (){
#Override
public void onSuccess() {
//hide progress and show success message
}
#Override
public void onFailure() {
//hide progress and show error
}
}
);
3- then modify your method logInWebService()
public static boolean logInWebService(Context context, final String usr, final String pass,ResultCallback callback) {
String url = "https://webpage.myPhpWS.php?"+"idusr="+usr+"&pass="+pass;
result = false
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
callback.onSuccess(); //you can pass sth to this method
if(someOperation){
result = true;
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
System.out.println("Error"+error.getMessage());
callback.onSuccess();
}
});
RequestQueue request = Volley.newRequestQueue(context);
request.add(jsonObjectRequest);
return result; //remove this and return void instead.
}
a better approach is to use Rxjava observables.
Ok , i had the same problem, i just solved it with ObservableInteger , just declare it
private ObservableInteger mObsInt;
then in onCreate setup a listener
//Listener
mObsInt = new ObservableInteger();
mObsInt.set(0);
mObsInt.setOnIntegerChangeListener(new OnIntegerChangeListener()
{
#Override
public void onIntegerChanged(int newValue)
{
if (mObsInt.get()==1)
Log.e("Downloads"," mObsInt 1");
Log.e("Download1"," Finished first process ");
if (mObsInt.get()==2){
Log.e("Downloads"," mObsInt 2");
Log.e("Download2"," Finished second process ");
mProgressDialog.dismiss();
Intent mainIntent = new Intent().setClass(LoginActivity.this, Principal.class);
startActivity(mainIntent);
finish();
}
}
});
and then just do this (after a process has finished)
mObsInt.set(mObsInt.get()+1);
so it will count, if the first thing finish obsInt will be 1 , and when the second one finish, obsInt will be 2, so after obsInt == 2 , you can move on to the other activity or process you need
happy coding !
I'm using mockito to mock AccountManager inside an Activity test.
So, my test code is as follows:
public class PressuresListActivityUnitTest extends
ActivityUnitTestCase<PressuresListActivity> {
// Test data.
private static final String ACCOUNT_TYPE = "com.example.android";
private static final Account ACCOUNT_1 = new Account("account1#gmail.com", ACCOUNT_TYPE);
private static final Account ACCOUNT_2 = new Account("account2#gmail.com", ACCOUNT_TYPE);
private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };
#Mock
private AccountManager mMockAccountManager;
public PressuresListActivityUnitTest() {
super(PressuresListActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
setupDexmaker();
// Initialize mockito.
MockitoAnnotations.initMocks(this);
}
public void testAccountNotFound() {
Mockito.when(mMockAccountManager.getAccounts())
.thenReturn(TWO_ACCOUNTS);
Intent intent = new Intent(Intent.ACTION_MAIN);
startActivity(intent, null, null);
}
/**
* Workaround for Mockito and JB-MR2 incompatibility to avoid
* java.lang.IllegalArgumentException: dexcache == null
*
* #see <a href="https://code.google.com/p/dexmaker/issues/detail?id=2">
* https://code.google.com/p/dexmaker/issues/detail?id=2</a>
*/
private void setupDexmaker() {
// Explicitly set the Dexmaker cache, so tests that use mockito work
final String dexCache = getInstrumentation().getTargetContext().getCacheDir().getPath();
System.setProperty("dexmaker.dexcache", dexCache);
}
And the onCreate mthod of activity that will be tested:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pressures_list);
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccounts();
if (accounts.length > 0) {
Log.i("TAG", "it works!");
}
}
But when I run the test, AccountManager.getAccounts does NOT return the accounts specified in the test.
Any idea?
After some research, I finally solved the problem.
Android provides some classes to be used inside the tests, like MockContext, IsolatedContext.
http://developer.android.com/reference/android/test/mock/MockContext.html
http://developer.android.com/reference/android/test/IsolatedContext.html
To get this done, I created a subclass of ContextWrapper and overrode(??) getSystemService method.
According to the documentation:
"Proxying implementation of Context that simply delegates all of its calls to another Context. Can be subclassed to modify behavior without changing the original Context."
http://developer.android.com/reference/android/content/ContextWrapper.html
This way, I injected the original context, but modified to fit my needs, inside the Activity using a regular AndroidActivityUnitTestCase.
Check this out:
public class FakeContextWrapper extends ContextWrapper {
private static final String ACCOUNT_TYPE = "com.example.android";
private static final Account ACCOUNT_1 = new Account("account1#gmail.com", ACCOUNT_TYPE);
private static final Account ACCOUNT_2 = new Account("account2#gmail.com", ACCOUNT_TYPE);
private static final Account[] TWO_ACCOUNTS = { ACCOUNT_1, ACCOUNT_2 };
#Mock
private AccountManager mMockAccountManager;
public FakeContextWrapper(Context base) {
super(base);
MockitoAnnotations.initMocks(this);
Mockito.when(mMockAccountManager.getAccounts()).thenReturn(TWO_ACCOUNTS);
}
#Override
public Object getSystemService(String name) {
if (Context.ACCOUNT_SERVICE.equals(name)) {
return mMockAccountManager;
} else {
return super.getSystemService(name);
}
}
}
Inside the test:
public void testAccountNotFound() {
Context context = new FakeContextWrapper(getInstrumentation().getTargetContext());
setActivityContext(context);
Intent intent = new Intent(Intent.ACTION_MAIN);
startActivity(intent, null, null);
// TODO assertions.
}
Finally, the Activity under test:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pressures_list);
AccountManager am = AccountManager.get(this);
Account[] accounts = am.getAccounts();
if (accounts.length == 0) {
// TODO call login.
} else {
Log.i("TAG", "it works!");
}
}
That's not how mockito works.
import static org.mockito.Mockito.*;
...
public void testAccountNotFound() {
AccountManager am = mock(AccountManager.class);
when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);
// this is how you unit test something
assertTrue(am.getAccounts().size == 2);
}
public void testMoreRealWorldExample() {
AccountManager am = mock(AccountManager.class);
when(am.getAccounts()).thenReturn(TWO_ACCOUNTS);
/* try and create an account; createNewAccount() will call
getAccounts() to find out how many accounts there already
are in the system, and due to the above injection, it would
think there are already two. Thus we can test to make sure
users cannot create three or more accounts.
*/
boolean accountCreated = am.createNewAccount();
// maximum two accounts are allowed, so this should return false.
assertFalse(accountCreated);
}
You can't directly use mockito to just arbitrarily inject values in objects and then run an Activity. Mockito is for unit testing your objects, ideally with minimal references to Android-specific objects, though some references will be inevitable.
Please read the cookbook more closely as it is pretty thorough.
If you want to mock and entire Activity, you'll need to look in to Robolectric
I'm making a android library project to use our API. Following MVC rules, I've created some proxies to centralize the usage of certain tasks. e.g. I have a LoginProxy which handles all the requests, data, ... involving logging in.
To reach this, you have to call LoginProxy.getInstance().methodname. When you want to log in, you have to call the login method on the proxy, which executes a AsyncTask, executing a webservice call to a server. When the answer of the call arrives back to the LoginProxy, the LoginProxy has to dispatch an event, notifying components (Activities) about changes. At this moment, I've made my own event dispatching mechanism. Activities using this LoginProxy have to add a EventListener to an event of the LoginProxy in order to be notified about changes. I've added my LoginProxy code.
public class LoginProxy extends EventDispatcher implements LoginUserListener {
private boolean loggedIn;
private boolean loggingIn;
private String userName;
private UserConfiguration userConfiguration;
private Credentials credentials;
private LoginProxy(){}
/**
* Gets a singleton instance of this class
*/
private static LoginProxy _instance;
public static LoginProxy getInstance(){
if(_instance == null) {
_instance = new LoginProxy();
}
return _instance;
}
public boolean isLoggedIn() {
return loggedIn;
}
private void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
dispatchEvent(new GeneralEvent(GeneralEventType.LOGGED_IN_CHANGED));
}
public boolean isLoggingIn() {
return loggingIn;
}
private void setLoggingIn(boolean loggingIn) {
this.loggingIn = loggingIn;
dispatchEvent(new GeneralEvent(GeneralEventType.LOGGING_IN_CHANGED));
}
public String getUserName() {
return userName;
}
private void setUserName(String userName) {
this.userName = userName;
}
public UserConfiguration getUserConfiguration() {
return userConfiguration;
}
private void setUserConfiguration(UserConfiguration userConfiguration) {
this.userConfiguration = userConfiguration;
}
public Credentials getCredentials() {
return credentials;
}
private void setCredentials(Credentials credentials) {
this.credentials = credentials;
}
public void login(String userName, String userPassword, String applicationId, String applicationPassword, String clientVersion){
setLoggingIn(true);
LoginUserLauncher launcher = new LoginUserLauncher(this);
launcher.loginUser(userName, userPassword, applicationId, applicationPassword, clientVersion);
setUserName(userName);
}
public void logout(){
setUserName(null);
setUserConfiguration(null);
setCredentials(null);
setLoggedIn(false);
}
#Override
public void onLoginUserDone(LoginUserLauncher sender, StatusCode responseStatusCode, UserConfiguration userConfiguration, String sessionCode) {
setLoggingIn(false);
if(responseStatusCode == StatusCode.OK){
setLoggedIn(true);
setUserConfiguration(userConfiguration);
Credentials c = new Credentials(userConfiguration.getUserID(), userConfiguration.getUserPassword(), sender.getApplicationId(), sender.getApplicationPassword(), sessionCode);
setCredentials(c);
dispatchEvent(new LoginEvent(LoginEventType.LOGIN_SUCCESS, responseStatusCode, this.userConfiguration));
}else{
setUserName(null);
setUserConfiguration(null);
setCredentials(null);
setLoggedIn(false);
dispatchEvent(new LoginEvent(LoginEventType.LOGIN_FAILED, responseStatusCode, this.userConfiguration));
}
}
}
I believe this works fine, but now I'm realizing that perhaps it's also possible to use Android Services for these proxies. Then I can send broadcasts instead of self made events. My question now is: Is it OK to use Service for these proxies? If so, I suppose that I have to make a aidl interface for binding?
Can anyone help me with this matter?
Thanks