Zhuinden / simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Apache License 2.0
1.36k stars 76 forks source link

Unable to start activity ComponentInfo{com.xxxxxxxxxx.yyyyyy/com.xxxxxxxxxx.yyyyyy.activities.MainActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment q2.v1: could not find Fragment constructor #269

Closed metinvio closed 1 year ago

metinvio commented 1 year ago

First of all thank you very much for this great sdk. It simplifies a lot of things and saves us a lot of time 🙏.

But I have a problem I'm struggling with since I'm using NavHost. I was hoping that simple-stack would solve the problem, but it still happens. I know it's not the SDK, but maybe someone here knows what I'm doing wrong.

It always happens when the app comes to the foreground after a long time, or when the app tries to restart after a crash. And it happens randomly for every fragment I have in my BottomNavigation. Sometimes it's the first tab, other times it's the third tab (/fragment). Sometimes it's could not find Fragment constructor and sometimes Error inflating class fragment. It also happens when you rotate the phone. But fortunately our app is portrait mode only. This is the stacktrace I get on Crashlytics:

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxxxxxxxxx.yyyyyy/com.xxxxxxxxxx.yyyyyy.activities.MainActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment q2.v1: could not find Fragment constructor
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3645)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3782)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2307)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:201)
       at android.os.Looper.loop(Looper.java:288)
       at android.app.ActivityThread.main(ActivityThread.java:7872)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

or

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.eixxip.mydividend24/com.eixxip.mydividend24.activities.MainActivity}: android.view.InflateException: Binary XML file line #68 in com.eixxip.mydividend24:layout/activity_main: Binary XML file line #68 in com.eixxip.mydividend24:layout/activity_main: Error inflating class fragment
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3851)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4027)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:247)
       at android.app.ActivityThread.main(ActivityThread.java:8676)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Zhuinden commented 1 year ago

@metinvio hello, glad to hear simple-stack is working well! Based on your stack trace (q2.v1) being obfuscated, I would expect that you either have Fragments with multiple constructors (you should only have no argument constructor on a fragment), or the Fragment's constructor is removed by R8 in release mode.

You could try these proguard-rules.pro additions:

-keepclassmembers public class * extends android.support.v4.app.Fragment { 
   public <init>(...);
}
-keepclassmembers public class * androidx.fragment.app.Fragment { 
   public <init>(...);
}

In general, I expect this to be a process death crash caused by having non-empty fragment constructor. You should always use empty constructor + pass args with setArguments() (with simple-stack and DefaultFragmentKey/DefaultFragmentStateChanger, this already happens and passes the @Parcelize data class YourKey(.. as an argument. If you extend from KeyedFragment then you can use getKey(), if you don't then requireArguments().getParcelable(DefaultFragmentKey.KEY_ARGS).)

metinvio commented 1 year ago

@Zhuinden thank you for the quick response. I doubt that this is caused by Proguard. Because it also happens in debug mode where proguard is disabled. But I think the second part of your answer is more likely the cause. I will check that and will let you know - thanks a lot 😊

Zhuinden commented 1 year ago

@metinvio Did you succeed in figuring out the cause?

metinvio commented 1 year ago

Hi @Zhuinden , no unfortunately it's still the same. I changed all my Fragment to this:

class MyFragment: Fragment() {
...
   companion object{
        fun newInstance(name : String) : MyFragment {
            val args = Bundle()
            args.putSerializable(ARG_NAME, name)
            val fragment = MyFragment()
            fragment.arguments = args
            return fragment
        }
    }
}

and I still get crash reports like following:

java.lang.NoSuchMethodException - o2.u2. []

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxxxxxxxx/com.xxxxxxxxx.activities.MainActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment o2.w2: could not find Fragment constructor
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4166)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4312)
       at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
       at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2571)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:226)
       at android.os.Looper.loop(Looper.java:313)
       at android.app.ActivityThread.main(ActivityThread.java:8741)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

In RootScreen I'm calling them like:

override fun bindServices(serviceBinder: ServiceBinder) {
        with(serviceBinder) {
            add(FragmentStackHost(Screen1()), SCREEN1_STACK)
            add(FragmentStackHost(Screen2()), SCREEN2_STACK)
            add(FragmentStackHost(Screen3()), SCREEN3_STACK)
            add(FragmentStackHost(Screen4)), SCREEN4_STACK)
            add(FragmentStackHost(Screen5()), SCREEN5_STACK)
        }
    }

and inside NavigationKeys.kt like:

@Parcelize
class Screen1 : DefaultFragmentKey() {
    override fun instantiateFragment(): Fragment = MyFragment.newInstance()
}
Zhuinden commented 1 year ago

Theoretically if you have a noarg constructor that you call from code, then it can't be removed by Proguard. It would be nice to know what o2.u2. and o2.w2 is based on your Proguard mapping file (it's in the build outputs) so that you can troubleshoot more.

Btw, make sure your keys are data class (or data object) as per the readme.

image

Zhuinden commented 1 year ago

@metinvio did you ever figure this out?

What is o2.w2 in your Proguard mapping, is it YOUR class? Maybe it's something like MapFragment used from <fragment and Proguard actually doesn't keep its constructor.

metinvio commented 1 year ago

Sorry for the late reply, I haven't had a chance to check it out yet. Ok, thanks to your advice regarding pro guard and mapping.txt, I was able to see where the crashes happen originally. To summarize: After reading you answers again carefully, I understood it in detail. The main problem was the constructor. I have removed them in all my fragments and added a companion object with a newInstance-Method. No crashes anymore! Thank you my friend, you saved me a lot of trouble - again! 🙏❤️