I want to start using Robolectric and RoboGuice in my Android-Apps. While I make satisfactory progress using Robolectric I am stuck using RoboGuice. I created a small Android-App for experimenting. It is only one Activity, injecting a Button and setting its OnClickListener.
In the related Test-Class I want to Inject this Activity, to be able to test the Button. I tried a lot of things I found all over the internet, but none of these worked, so I give it a go here.
Here is some code:
MainActivity.java:
package com.example.TrialApp;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import roboguice.activity.RoboActivity;
import roboguice.inject.InjectView;
public class MainActivity extends RoboActivity implements View.OnClickListener {
#InjectView(R.id.main_LoginButton) private Button loginButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loginButton.setOnClickListener(this);
}
#Override
public void onClick(View view) {
SharedPreferences.Editor editor;
if (view.getId() == R.id.main_Login_Button)
Log.i("Login-Button pressed... ", "");
}
}
CustomRobolectricTestRunner.java:
package com.example.TrialApp;
import com.xtremelabs.robolectric.RobolectricTestRunner;
import org.junit.runners.model.InitializationError;
import java.io.File;
public class CustomRobolectricTestRunner extends RobolectricTestRunner {
public CustomRobolectricTestRunner(Class testClass) throws InitializationError {
// defaults to "AndroidManifest.xml", "res" in the current directory
super(testClass, new File("TrialApp"));
}
}
MainActivity_Test.java:
package com.example.TrialApp;
import com.google.inject.Inject;
import com.xtremelabs.robolectric.Robolectric;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertNotNull;
#RunWith(CustomRobolectricTestRunner.class)
public class MainActivity_Test {
#Inject MainActivity mainActivity;
#Inject ClassWithoutAName classWithoutAName;
#Before
public void setUp() {
}
#Test
public void mainActivityShouldNotBeNull() {
assertNotNull(mainActivity);
}
#Test
public void classWithoutANameShouldNotBeNull() {
assertNotNull(classWithoutAName);
}
}
classWithoutAName is just a non-Activity-class with no content. I added just for injecting a non-Activity-class.
Running the Test-Class both tests fail giving the following errors:
java.lang.AssertionError
at com.example.TrialApp.MainActivity_Test.mainActivityShouldNotBeNull(MainActivity_Test.java:33) <8 internal calls>
at com.xtremelabs.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:288) <16 internal calls>
and
java.lang.AssertionError
at com.example.TrialApp.MainActivity_Test.classWithoutANameShouldNotBeNull(MainActivity_Test.java:38) <8 internal calls>
at com.xtremelabs.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:288) <16 internal calls>
Obviously something is missing. Injecting in MainActivity works fine, and the app is running.
Why is the same pattern of injecting dependencies in a Robolectric-Testclass not working? Where is the missing link?
Thank you
After reading some more articles and threads I found a partial solution. I still haven't found a way to inject UI-elements in a testclass, but I found out, how to inject non-UI-elements in a testclass. The trick is to implement a "RobolectricTestModule" extending the AbstractModule. The bindings made in the modules declared in the roboguice.xml are not present in the test-environment, so we need to declare the bindings for the test-environment in this extra-module. In the customized Testrunner we replace the DefaultRoboModule with the new RobolectricTestModule.
ClassWithoutAName.java:
package com.example.TrialApp;
public class ClassWithoutAName {
private String string;
public ClassWithoutAName(String string) {
this.string = string;
}
public String getString() {
return string;
}
}
ClassWithoutANameProvider.java
package com.example.TrialApp.GuiceModules;
import com.example.TrialApp.ClassWithoutAName;
import com.google.inject.Provider;
public class ClassWithoutANameProvider implements Provider<ClassWithoutAName> {
#Override
public ClassWithoutAName get() {
return new ClassWithoutAName("testString");
}
}
ClassWithOutANameModule.java
package com.example.TrialApp.GuiceModules;
import com.example.TrialApp.ClassWithoutAName;
import com.google.inject.AbstractModule;
public class ClassWithOutANameModule extends AbstractModule {
#Override
protected void configure() {
bind(ClassWithoutAName.class).toProvider(ClassWithoutANameProvider.class);
}
}
RobolectricTestModule.java
package com.example.TrialApp.GuiceModules;
import com.example.TrialApp.WeirdThings;
import com.google.inject.AbstractModule;
public class RobolectricTestModule extends AbstractModule {
#Override
protected void configure() {
bind(ClassWithoutAName.class).toProvider(ClassWithoutANameProvider.class);
}
}
CustomRobolectricTestRunner.java
package com.example.TrialApp;
import android.app.Application;
import com.example.TrialApp.GuiceModules.RobolectricTestModule;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.RobolectricTestRunner;
import org.junit.runners.model.InitializationError;
import roboguice.RoboGuice;
import java.io.File;
public class CustomRobolectricTestRunner extends RobolectricTestRunner {
public CustomRobolectricTestRunner(Class testClass) throws InitializationError {
// defaults to "AndroidManifest.xml", "res" in the current directory
super(testClass, new File("TrialApp"));
}
#Override
public void prepareTest(Object test) {
Application application = (Application) Robolectric.application;
RoboGuice.setBaseApplicationInjector(application, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(application), new RobolectricTestModule());
RoboGuice.getInjector(application).injectMembers(test);
}
}
Thats it for the non-UI-Elements.
Related
I am trying to use the android_alarm_manager_plus package to schedule background tasks in flutter. In their documentation, they have added the Application class code for JAVA, which is as follows -
public class Application extends FlutterApplication implements PluginRegistrantCallback {
#Override
public void onCreate() {
super.onCreate();
AlarmService.setPluginRegistrant(this);
}
#Override
public void registerWith(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
}
But I'm working on a kotlin based project (without any knowledge of kotlin, I may add) and there is no documentation for this. I tried writing the kotlin counterpart to this code myself, but I'm still facing errors.
My Application.kt file -
package com.example.my_app;
import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.AndroidAlarmManager.AlarmService;
class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
AlarmService.setPluginRegistrant(this);
}
override fun registerWith(registry: PluginRegistry?) {
registry?.registrarFor("GeneratedPluginRegistrant");
}
}
The errors -
Application.kt: (7, 27): Unresolved reference: AndroidAlarmManager
Application.kt: (15, 9): Unresolved reference: AlarmService
You do not need to add this Java/Kotlin code. That's for the old Android embedding. Note the "DEPRECATED" here in the title: https://github.com/fluttercommunity/plus_plugins/tree/main/packages/android_alarm_manager_plus#flutter-android-embedding-v1-deprecated
I'm using firebase_messaging in my flutter application.
To handle background messages with firebase messaging in pub they suggested to create new Application.java file and replace java file name in AndroidManifest file.
In my application i'm using kotlin and i already implemented some native code in MainActivity.kt
So how to write this code in kotlin.
package io.flutter.plugins.firebasemessagingexample;
import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService;
public class Application extends FlutterApplication implements PluginRegistrantCallback {
#Override
public void onCreate() {
super.onCreate();
FlutterFirebaseMessagingService.setPluginRegistrant(this);
}
#Override
public void registerWith(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
}
it is mandatory to replace MainActivity to Application in AndroidManifest file?
Here is the working background notification kotlin code:
package com.example.yourapp
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
FlutterFirebaseMessagingService.setPluginRegistrant(this);
}
override fun registerWith(registry: PluginRegistry?) {
io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));
}
}
Here is the Kotlin code for the new firebase cloud messaging version:
package id.your.app
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService
// import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin
class Application : FlutterApplication(), PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
FlutterFirebaseMessagingBackgroundService.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry?) {
// FlutterFirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin"))
}
}
The code is
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.schedulers.Schedulers;
public class BasePresenter {
protected <T> void subscribe(Observable<T> observable, Observer<T> observer) {
observable.subscribeOn(Schedulers.newThread())
.toSingle()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
The problem was that I couldn't use AndroidSchedulers because Observable was 1.x version. I changed import's and now the problem is that i can't use .toSingle
I am just testing RxJava. so, please don't be angry if it is stupid question.
I'm using Dagger 2 in Android Studio, but when try to create Component through DaggerMyComponent.builder().myModule(new MyModule()).build(), I always see the word "builder()" in red and it says "cannot resolve symbol builder".
I must say that I've cleaned and builted many times the project, even commenting the component instance and decommenting it after rebuild.
I must also say that I can inspect the DaggerMyComponent.class, and everything seems ok, in fact, I can import that class with no problem.
The example is shared on GithHub, at this link:
https://github.com/alessandroargentieri/LetsDagger2Together
I have two Classes: BClass and AClass which depends on the first one.
I have ABModule.class which is the factory method and ABComponent which is the component interface.
The DaggerABComponent is created into WholeApplication.class which extends Application.
package mawashi.alex.letsdagger2together.Application;
import android.app.Application;
import mawashi.alex.letsdagger2together.DaggerClasses.ABComponent;
import mawashi.alex.letsdagger2together.DaggerClasses.ABModule;
import mawashi.alex.letsdagger2together.DaggerClasses.DaggerABComponent;
public class WholeApplication extends Application {
static ABComponent component;
#Override
public void onCreate() {
super.onCreate();
//here is where builder() is not recognized
component = new DaggerABComponent.builder().aBModule(new ABModule()).build();
}
public static ABComponent getComponent(){
return component;
}
}
Module:
package mawashi.alex.letsdagger2together.DaggerClasses;
import dagger.Module;
import dagger.Provides;
import mawashi.alex.letsdagger2together.Model.AClass;
import mawashi.alex.letsdagger2together.Model.BClass;
#Module
public class ABModule {
#Provides
public BClass provideBClass(){
return new BClass("xxx");
}
#Provides
public AClass provideAClass(BClass bClass){
return new AClass(bClass);
}
}
Component:
package mawashi.alex.letsdagger2together.DaggerClasses;
import dagger.Component;
import mawashi.alex.letsdagger2together.MainActivity;
#Component (modules = {ABModule.class})
public interface ABComponent {
public void inject(MainActivity mainActivity);
}
Classes which are to be injected:
package mawashi.alex.letsdagger2together.Model;
public class BClass {
private String xfactor;
public BClass(String xfactor){
this.xfactor = xfactor;
}
public String getXfactor(){
return xfactor;
}
}
package mawashi.alex.letsdagger2together.Model;
public class AClass {
private BClass b;
public AClass(BClass b){
this.b = b;
}
public String getYfactor(){
return "Y-" + b.getXfactor() + "-Y";
}
}
Place where to inject AClass and BClass:
package mawashi.alex.letsdagger2together;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import javax.inject.Inject;
import mawashi.alex.letsdagger2together.Application.WholeApplication;
import mawashi.alex.letsdagger2together.DaggerClasses.ABModule;
import mawashi.alex.letsdagger2together.DaggerClasses.DaggerABComponent;
import mawashi.alex.letsdagger2together.Model.AClass;
public class MainActivity extends AppCompatActivity {
#Inject AClass a;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WholeApplication.getComponent().inject(this);
Toast.makeText(this, a.getYfactor(),Toast.LENGTH_LONG).show();
}
}
If anyone knows what I mistake it would be very nice to me. Thanks to everybody.
Oh, I've figured it out.
You added this line:
component = new DaggerABComponent.builder().aBModule(new ABModule()).build();
And it should be this line:
component = DaggerABComponent.builder().aBModule(new ABModule()).build();
So just remove new and then it'll work.
All fields with #Inject annotation must be public:
#Inject public AClass a; in your Activity
I started setting up dependency injection using Dagger as follows. Please feel encouraged to correct my implementation since I might have mistakes in there! The implementation follows the android-simple example provided by the project. In the following you can see how I successfully added dependency injection for Activities and Fragments. I try to keep it easy for now so I decided to inject Timber as a logger substitution for Android's log util.
import android.app.Application;
import java.util.Arrays;
import java.util.List;
import dagger.ObjectGraph;
import com.example.debugging.LoggingModule;
public class ExampleApplication extends Application {
private ObjectGraph mObjectGraph;
protected List<Object> getModules() {
return Arrays.asList(
new AndroidModule(this),
new ExampleModule(),
new LoggingModule()
);
}
private void createObjectGraphIfNeeded() {
if (mObjectGraph == null) {
Object[] modules = getModules().toArray();
mObjectGraph = ObjectGraph.create(modules);
}
}
public void inject(Object object) {
createObjectGraphIfNeeded();
mObjectGraph.inject(object);
}
}
By now the AndroidModule is not used anywhere it but might be helpful when a Context and LayoutInflater is needed e.g. in CursorAdapters.
import android.content.Context;
import android.view.LayoutInflater;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
/**
* A module for Android-specific dependencies which require a {#link Context}
* or {#link android.app.Application} to create.
*/
#Module(library = true)
public class AndroidModule {
private final ExampleApplication mApplication;
public AndroidModule(ExampleApplication application) {
mApplication = application;
}
/**
* Allow the application context to be injected but require that it be
* annotated with {#link ForApplication #Annotation} to explicitly
* differentiate it from an activity context.
*/
#Provides #Singleton #ForApplication Context provideApplicationContext() {
return mApplication;
}
#Provides #Singleton LayoutInflater provideLayoutInflater() {
return (LayoutInflater) mApplication
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
}
I am not sure what application-specific providers would go here. I stay with logging for now.
import dagger.Module;
#Module(
complete = false
)
public class ExampleModule {
public ExampleModule() {
// TODO put your application-specific providers here!
}
}
I prepared LoggingModule which provides access to Timber.
package com.example.debugging;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import com.example.BuildConfig;
import com.example.activities.BaseFragmentActivity;
import com.example.activities.DetailsActivity;
import com.example.fragments.BaseListFragment;
import com.example.fragments.ProfilesListFragment;
import timber.log.Timber;
#Module(injects = {
// Activities
BaseFragmentActivity.class,
DetailsActivity.class,
// Fragments
BaseListFragment.class,
ProfilesListFragment.class
})
public class LoggingModule {
#Provides #Singleton Timber provideTimber() {
return BuildConfig.DEBUG ? Timber.DEBUG : Timber.PROD;
}
}
The base class for Activities injects itself into the object graph ...
package com.example.activities;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;
public abstract class BaseFragmentActivity extends SherlockFragmentActivity {
#Inject Timber mTimber;
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
super.onCreate(savedInstanceState);
((ExampleApplication) getApplication()).inject(this);
}
}
... and any sub class benefits from Timber being already present.
package com.example.activities;
import android.os.Bundle;
import com.example.R;
public class DetailsActivity extends BaseFragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
mTimber.i("onCreate");
// ...
}
}
Same for Fragments: the base class does the dirty job ...
package com.example.fragments;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockListFragment;
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;
public abstract class BaseListFragment extends SherlockListFragment {
#Inject Timber mTimber;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((ExampleApplication) getActivity().getApplication()).inject(this);
}
}
... and the sub class benefits from its super class.
package com.example.fragments;
import android.os.Bundle;
public class ProfilesListFragment extends BaseListFragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// TODO This might be a good example to inject resources
// in the base class. But how?
setEmptyText(getResources()
.getString(R.string.profiles_list_no_content));
mTimber.i("onActivityCreated");
// ...
}
}
So far so good. But how can inject Timber into BaseCursorAdapter, BaseContentProvider, BaseSQLiteOpenHelper, BaseService, BaseAsyncTask and static helper methods?
The deprecated android example by Christopher Perry points out how to inject an Adapter into a ListFragment but not how to inject Context, Resources, LayoutInflater, Cursor into the (Cursor)Adapter or just Timber.
References:
Dagger
android-simple example
Jesse Wilson - Dagger: A Fast Dependency Injector for Android and Java
Eric Burke - Android App Anatomy
Check out Andy Dennie's examples for injecting in different scenarios:
https://github.com/adennie/fb-android-dagger
Some points where I inject:
Activity, Service, and Fragment subclasses: in onCreate
BroadcastReceiver subclasses (includes, e.g. AppWidgetProvider): in onReceive
tl;dr There's a lot going on in this question, but it might be helped with a Gist of mine. If you can get a Context somewhere you can inject it based off of the ObjectGraph maintained by your Application class.
Edit by JJD:
The ObjectGraph in the Gist can be integrated as follows:
public class ExampleApplication extends Application
implements ObjectGraph.ObjectGraphApplication {
/* Application Lifecycle */
#Override
public void onCreate() {
// Creates the dependency injection object graph
_object_graph = ObjectGraph.create(...);
}
/* ObjectGraphApplication Contract */
#Override
public void inject(#Nonnull Object dependent) {
_object_graph.inject(dependent);
}
/** Application's object graph for handling dependency injection */
private ObjectGraph _object_graph;
}
...
public abstract class BaseFragmentActivity extends SherlockFragmentActivity {
#Inject Timber _timber;
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
ObjectGraph.inject(this);
}
}
...
public abstract class BaseListFragment extends SherlockListFragment {
#Inject Timber _timber;
#Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
ObjectGraph.inject(this);
}
}
Similar works for BaseCursorAdapter, BaseContentProvider and BaseService.
We had to do this very same thing (i.e. inject a logger everywhere). We ended up making a very small static wrapper (thus available everywhere) that wraps a class that is injected via dagger statically.
package com.example.secret;
import javax.inject.Inject;
import com.example.interfaces.Logger;
public class LoggerProvider {
#Inject
static Logger logger;
public LoggerProvider() {
}
public static Logger getLogger() {
return logger;
}
}
Logger implements your logging interface. To inject it at the application level you need:
graph = ObjectGraph.create(getModules().toArray());
graph.injectStatics();
Code here: https://github.com/nuria/android-examples/tree/master/dagger-logger-example
Injection of Context, Resources and LayoutInflater (passing the application context when you new this up in Application).
#Module(complete = false)
public class AndroidServicesModule {
private final Context context;
public AndroidServicesModule(#ForApplication Context context) {
this.context = context;
}
#Provides #Singleton Resources provideResources() {
return context.getResources();
}
#Provides #Singleton LocationManager provideLocationManager() {
return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
#Provides #Singleton LayoutInflater provideLayoutInflater() {
return (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Provides #Singleton Resources provideResources() {
return context.getResources();
}
#Provides #ForApplication Context provideContext() {
return context;
}
}
Of course you should probably qualify the context with an annotation specifying it's the application context (for example #ForApplication).
If you need the equivalent of Roboguice's #InjectResource(int) I can't think of anything offhand. Butterknife seems like the right lib to add this to. See here
You can add these constructions to ExampleApplication class:
private static ExampleApplication INSTANCE;
#Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
mObjectGraph = ObjectGraph.create(getModules());
mObjectGraph.injectStatics();
mObjectGraph.inject(this);
}
public static ExampleApplication get() {
return INSTANCE;
}
After that you are able to inject any object (denoted by this) with one simple line:
ExampleApplication.get().inject(this)
This should be called in constructor for objects you create manually or onCreate (or analog) for those that are managed by Android System.