I'm experiencing something strange, that never happened to me before.
This is simple class that extends an Application class:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
}
}
It is a library module, than I try to extend it from an application module:
public class MyApp extends MyApplication {
#Override
public void onCreate() {
super.onCreate();
Log.i("MyApp", "Application created");
}
}
Now when compiling with minifyEnabled=true I'm getting strange result:
In library module class get changed to (note the final keyword):
public class MyApplication extends Application {
MyApplication() {
}
public final void onCreate() {
super.onCreate();
}
}
And this causes compilation error, since MyApp cannot override a final method.
Did someone faced this issue?
Thanks in advance.
Happens only with minify enabled. Class MyApplication is added to proguard-rules as exception.
Happens only on Gradle > 7.1.3. Reverting to 7.1.3 fixes the cause.
Related
I have a CustomApplication extends Application class, which is registered in AndroidManifest
<application
....
// Please, pay attention that I got this in my Manifest
android:name=".CustomApplication">
And at different part of my application, both some activities and services I do
getApplication()/getApplicationContext() then cast it to CustomApplication and it crashes in production on a variety of devices/sdk versions(beginning at android 6) due to a class cast exception. Caused by: java.lang.ClassCastException
Example:
class CustomApplication extends Application{
...
public static CustomApplication with(Context context) {
return (CustomApplication) context.getApplicationContext(); //crashes here
}
}
and service example:
class CustomService extends IntentService{
...
#Override
rotected void onHandleIntent(#Nullable Intent intent) {
CustomApplication app = CustomApplication.from(getApplication());
// tried getApplicationContext() also
}
}
and activity example:
class CustomActivity extends AppCompatActivity{
...
#Override
protected void onCreate(...){
CustomApplication app = CustomApplication.with(this);
}
What I've tried:
Tried services with different process=":process"
Tried deep linking with different launchModes
Tried activities with taskAffinity
launching from push notifications
process cleaning with system tray(on device), ps kill int adb shell
nothing helps me to reproduce an issue on emulator
I don't use Instant Run also (never used it)
Please don't provide me with suggests of using static application context instance
You can keep a static reference of your CustomApplication like below. You don't need to cast in the following way.
public class CustomApplication extends Application {
private static CustomApplication instance;
#Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static CustomApplication getContext() {
return instance;
}
}
Then call CustomApplication.getContext();
You need to define your custom application in the manifest as follow:
<application
....
android:name="my.package.path.CustomApplication">
... activities ....
</application>
Also, you are getting an instance of a class that extends Application, not Context, that being said you should call this the following way:
CustomApplication customApplication;
customApplication = (CustomApplication)getApplication();
What you might have to apply in case you have BroadcastReceiver(No context available) is:
customApplication = (CustomApplication)getApplicationContext().getApplication();
I'm using Dagger2 to inject my dependencies in all of my applications.
Some days ago I started getting Crash Reports for one of the applications from Samsung Android 7.0 (only these) devices.
java.lang.RuntimeException:
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2924)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2985)
..
Caused by: java.lang.ClassCastException:
at de.package.name.MyApplication.get(MyApplication.java:43)
at de.package.name.ui.base.BaseActivity.onCreate(BaseActivity.java:53)
at de.package.name.ui.startup.StartupActivity.onCreate(StartupActivity.java:26)
at android.app.Activity.performCreate(Activity.java:6912)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2877)
MyApplication class:
public class MyApplication extends MultiDexApplication {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
setupAppComponent();
}
private void setupAppComponent() {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.userApiModule(new UserApiModule())
.build();
appComponent.inject(this);
}
public static MyApplication get(Context context) {
return (MyApplication) context.getApplicationContext();
}
}
Relevant part of the BaseActivity class:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.get(this).getAppComponent().inject(this);
}
And finally, the StartupActivity Part:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupComponent(MyApplication.get(this).getAppComponent());
setContentView(R.layout.activity_startup);
startupPresenter.bindView(this);
}
public void setupComponent(AppComponent appComponent) {
startupComponent = DaggerStartupComponent.builder()
.appComponent(appComponent)
.startupModule(new StartupModule())
.build();
startupComponent.inject(this);
}
I already updated Dagger to the most recent version (2.11 for now). But I don't have any ideas about this issue. Also, I can't reproduce it on my Samsung S8 7.0 device.
So if you have any ideas, please let me know!
Cheers
edit:
If anyone runs into this problem. Take a look here: RuntimeException with Dagger 2 on Android 7.0 and Samsung devices
This might be your solution.
This has nothing to do with Dagger. The problem is here:
return (MyApplication) context.getApplicationContext();
The Context returned by getApplicationContext() is not guaranteed to be your Application instance. The only situation I've encountered where it wasn't was in an emulator, but it's always possible.
I prefer this approach:
private static MyApplication gInstance;
#Override
public void onCreate() {
gInstance = this;
}
public static MyApplication instance() {
return gInstance;
}
This is safe because the Application instance is created and its onCreate is called before any other Android component is created.
I am trying to ShadowClass Crashlytics/Fabric so that Robotlectric 3 tests do not fail. What I have so far is this:
The custom test runner that adds the Shadow class for Fabric:
public class TestRunner extends RobolectricGradleTestRunner {
public TestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
protected ShadowMap createShadowMap() {
return super.createShadowMap()
.newBuilder().addShadowClass(ShadowFabric.class).build();
}
#Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedClass(ShadowFabric.class.getName());
return builder.build();
}
}
The shadow class for Fabric:
#Implements(Fabric.class)
public class ShadowFabric {
#Implementation
public static Fabric with(Context context, Kit... kits) {
System.out.println("Shadowing Fabric");
return null;
}
}
My application class for my app:
public class MyApp extends Application {
#Override
public void onCreate() {
setupCrashlytics();
}
protected void setupCrashlytics() {
Crashlytics crashlyticsKit = new Crashlytics.Builder().core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()).build();
// Initialize Fabric with the debug-disabled crashlytics.
Fabric.with(this, crashlyticsKit);
}
}
And here is the test that passes in Debug (because Crashlytics is disabled on it), but fails in release mode because the ShadowClass is not working correctly:
#RunWith(TestRunner.class)
#Config(constants = BuildConfig.class, sdk=21, packageName="com.my.release.package.name", shadows={ShadowFabric.class})
public class MyTest {
#Test
public void testGreenDAOsave() {
// blah
}
}
The error I get with Crashlytics / Fabric during the test is the following:
STANDARD_ERROR
io.fabric.sdk.android.services.concurrency.UnmetDependencyException: com.crashlytics.android.core.CrashlyticsMissingDependencyException:
This app relies on Crashlytics. Please sign up for access at https://fabric.io/sign_up
install an Android build tool and ask a team member to invite you to this app's organization.
The stack trace shows that MyApp.setupCrashlytics() is being called and Fabric.with() is failing.
1) YES, the app is registered with Crashlytics.
2) YES, I did contact Crashlytics support email. I was told 'Robolectric is not supported'.
From what I can see, I just need to get the shadow class thing working and then Crashlytics will get shadowed and not init'd.
Ideas / Help would be very much appreciated!
This is my usual advice how to write a test against something not testable.
Extract you Fabric initialisation to protected method:
public class <MyApplicationName> {
public void onCreate() {
initFabric();
}
#VisibileForTesting
void initFabric() {
....
}
}
Create Test<MayApplicationName> class in test sources (same package and override Fabric initialisation:
public class Test<MyApplicationName> {
void initFabric() {
//nothing to do
}
}
Everywhere where you need using Fabric use DI (Dependency Injection) to mock Fabric in tests. Even more, I would suggest you create Analytics/Crash/Distribution class and hide Fabric usage from entire application.
And final you have left classes that wrap/hide the Fabric. Here you can write a custom shadow, spy on the real object or leave it untested. And you already tried to write custom shadow without success, also, spying is not an option here.
Happy coding!
Sugar ORM requires me to set my android:name as com.orm.SugarApp to function.
How can I use other libraries that also require my android:name to be changed?
I try :
public class Manage extends SugarApp {
#Override
public void onCreate() {
super.onCreate();
// do any thing
}
}
<application
android:name=".Manage"
...
>
t doesn't save any thing!
and after i remove app and test with
<application
android:name="com.orm.SugarApp"
...
>
it works but my stuf doesn't
In Pre-Versions It shows Force Close but in current version 1.4 ican solve it easy
I made Class Extends SugarApp but Sugar Context Didn't Initialized
public class Manage extends SugarApp {
// Here This Method Overrideed
#Override
public void onCreate() {
super.onCreate();
SugarContext.init(this); // Here This Method Called
LTH.dLog("Manage_TAG", "On Create Manage Class");
}
// Here This Method Overrideed
#Override
public void onTerminate() {
super.onTerminate();
SugarContext.terminate(); // Here This Method Called
LTH.dLog("Manage_TAG", "On Terminate Manage Class");
}
}
I am currently upgrading robolectric from version 1 to 2. In my current version I use the following to provide the test module (for binding) to roboguice.
public class RoboTestRunner extends RobolectricTestRunner {
public RoboTestRunner(Class<?> testClass) throws
InitializationError {
super(testClass);
}
#Override
public void prepareTest(Object test) {
Application app = Robolectric.application;
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app)).with(new
TestModule()));
Injector injector = RoboGuice.getInjector(app);
injector.injectMembers(test);
}
}
However now I have upgraded the prepareTest method is not in this class. Where should I run this code in the new version?
UPDATE
I have found the way to do this. I need to create an class which extends android.app.Application in the project and reference this in the Manifest. Then I create a class like so
public class TestApplication extends Application implements TestLifecycleApplication {
#Override
public void onCreate() {
super.onCreate();
RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(this), new TestModule());
}
#Override
public void beforeTest(Method method) {}
#Override
public void prepareTest(Object test) {
TestApplication application = (TestApplication) Robolectric.application;
RoboGuice.setBaseApplicationInjector(application, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(application), new TestModule());
RoboGuice.getInjector(application).injectMembers(test);
}
#Override
public void afterTest(Method method) {}
}
As this class has Test at the start robolectric should automatically find it and use it. However this doesn't seem to be happening. Does anybody know why?
UPDATE 2
This blog would suggest that the testmodule needs to be in the same package however I have all tests in a different package. How do I work around this?
Your TestApplication class should extend your own Application class, not android.app.Application, and it should be in the same package as your Application.
... however I have all tests in a different package.
That shouldn't be a problem. Put your TestApplication in your test module, but use the package from Application.
e.g., if you're using maven, the files would live here:
src/main/java/com/example/Application.java
src/test/java/com/example/TestApplication.java