8

I am developing Android app using Kotlin.In my app contains Tab with ViewPager so i implement the two tabs.when i move to another activity and compack to tab view activity the app getting fource stop and the logcat shows below the error.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.crypto.wallet/com.crypto.wallet.activities.MainActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.crypto.wallet.activities.ReceiveFragment: could not find Fragment constructor

MyFragment.kt

@SuppressLint("ValidFragment")
class ReceiveFragment(private val transactionList: List<TransactionEntity>, val appDatabase: AppDatabase, private val direction: TransactionAdapterDirection,
                      val networkDefinitionProvider: NetworkDefinitionProvider) : Fragment(){

    private var linearLayoutManager: LinearLayoutManager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        val rootView = inflater.inflate(R.layout.receive_fragment, container, false)
        val recyclerView = rootView.findViewById<RecyclerView>(R.id.transaction_recycler_in) as RecyclerView
        linearLayoutManager = LinearLayoutManager(getActivity(), LinearLayout.VERTICAL, false)
        recyclerView.layoutManager = linearLayoutManager
        recyclerView.adapter = TransactionRecyclerAdapter(transactionList,appDatabase,direction,networkDefinitionProvider)
        recyclerView.setHasFixedSize(true);
        return rootView
    }
}

ViewPager.kt

override fun getItem(position: Int): Fragment? {
        var fragment: Fragment? = null
        when (position) {
            //0 -> fragment = ReceiveFragment(MytransactionList,MyappDatabase,Myincoming,MynetworkDefinitionProvider)
            0 -> fragment = ReceiveFragment(MyIT,MyAppDatabase,MyIncoming,MyNetwork)
            1 -> fragment = SendFragment()
        }

        return fragment
    }

Main.kt OnCreate()

val viewPager = findViewById<ViewPager>(R.id.viewpager)
                if (viewPager != null) {
                    val adapter = ViewPagerAdapter(supportFragmentManager,it,appDatabase,INCOMING,networkDefinitionProvider)
                    viewPager.adapter = adapter
                }

 val pref = applicationContext.getSharedPreferences("MyPref", 0) // 0 - for private mode
                val editor = pref.edit()
                val gson = Gson()
                val json = gson.toJson(it)
                editor.putString("MyObject", json)
                editor.apply()

OnResume()

val prefs = applicationContext.getSharedPreferences("MyPref", 0)
        val gson = Gson()
        val json = prefs.getString("MyObject", "")
        val person1: List<TransactionEntity> = gson.fromJson(json, ArrayList<TransactionEntity>()::class.java)

UPDATE: After i tried this i get same error(i tried both Bundle and Shared pref):

companion object {
        @JvmStatic
        fun newInstance(myIT: List<TransactionEntity>, myAppDatabase: AppDatabase, myIncoming: TransactionAdapterDirection,
                        myNetwork: NetworkDefinitionProvider,
                        bundle: Bundle) =
                ReceiveFragment(myIT, myAppDatabase, myIncoming, myNetwork,bundle).apply {
                    arguments = Bundle().apply {
                       /* val prefs = getActivity()!!.getApplicationContext().getSharedPreferences("myPrefs", 0)
                        val gson = Gson()
                        val json = prefs.getString("MyObject", "")
                        val person1: List<TransactionEntity> = gson.fromJson(json,
                                ArrayList<TransactionEntity>()::class.java)
                        Log.d("Karthi", "Frag-GSON " + person1)*/

                        val dd = bundle.getSerializable("MySerializable")
                        Log.d("Karthi", "Frag-GSON " + dd)
                    }
                }
    }

Main.kt:

val bundle = Bundle()
                val gson_budle = Gson()
                val json_bundle = gson_budle.toJson(it)
                bundle.putSerializable("MySerializable", json_bundle)

                val sharedPreferences = getSharedPreferences(myPreferences, Context.MODE_PRIVATE)
                val editor = sharedPreferences.edit()
                val gson = Gson()
                val json = gson.toJson(it)
                editor.putString("MyObject", json)
                editor.putString("MyObject_json_bundle", json_bundle)
                Log.d("Karthi","After - IT" + json)
                Log.d("Karthi","After - IT" + json_bundle)
                editor.apply()

FATAL EXCEPTION: main Process: com.crypto.wallet, PID: 29313 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.crypto.wallet/com.crypto.wallet.activities.MainActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.crypto.wallet.activities.ReceiveFragment: could not find Fragment constructor at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:152) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:330) at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84) at com.crypto.wallet.activities.MainActivity.onCreate(MainActivity.kt:194)

Karthikeyan
  • 385
  • 3
  • 7
  • 18

3 Answers3

12

When you get short description over the error you will see :

Avoid non-default constructors in fragments: use a default constructor plus Fragment setArguments(Bundle) instead less. From the Fragment documentation:

Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().

for example :

 fun newInstance(bundle : Bundle) : ReceiveFragment{
        val fragment = ReceiveFragment()
         fragment.arguments=bundle           
        return fragment
    }

Now call the fragment :

 0 -> fragment = ReceiveFragment.newInstance(new Bundle)

More info: http://developer.android.com/reference/android/app/Fragment.html#Fragment()

Hemant Parmar
  • 3,924
  • 7
  • 25
  • 49
4

What you can do, is to make a default constructor with zero parameters

companion object {
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment LoginFragment.
     */
    // TODO: Rename and change types and number of parameters
    @JvmStatic
    fun newInstance(param1: String, param2: String) =
            ReceiveFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
// Or from your code, you can use Gson
   val prefs = applicationContext.getSharedPreferences("MyPref", 0)
   val gson = Gson()
   val json = prefs.getString("MyObject", "")
   val person1: List<TransactionEntity> = gson.fromJson(json, 
   ArrayList<TransactionEntity>()::class.java)

            }
}

And you can use it like

   0 -> receiveFragment= ReceiveFragment.newInstance(firstParam, secondParam)

And you can receive this params in onCreate

 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    arguments?.let {
        email = it.getString(ARG_PARAM1)
        password = it.getString(ARG_PARAM2)

    }
  // Or from your code, you can use Gson
   val prefs = applicationContext.getSharedPreferences("MyPref", 0)
   val gson = Gson()
   val json = prefs.getString("MyObject", "")
   val person1: List<TransactionEntity> = gson.fromJson(json, 
   ArrayList<TransactionEntity>()::class.java)
}

And your fragment constructor like this

class ReceiveFragment: Fragment()

Hope this helps

Abdul Kawee
  • 2,687
  • 1
  • 14
  • 26
  • yea . . and i have doubt on this, my parameters are not String types its custom types.like a "List" so how can i add to putString() . ...?????? e.g:fun newInstance(myIT: List, myAppDatabase: AppDatabase, myIncoming: TransactionAdapterDirection, myNetwork: NetworkDefinitionProvider) – Karthikeyan Sep 11 '18 at 05:23
  • so simply replace with your params , this is for example :) – Abdul Kawee Sep 11 '18 at 05:23
  • okay without parameter it does not work right . here can i use GSON?? – Karthikeyan Sep 11 '18 at 05:26
  • yes definitely you can, in `newInstance` method, let me edit a bit for you – Abdul Kawee Sep 11 '18 at 05:27
  • i need to get this params in newInstance() not in oncreate() for this " putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2)" i need to use GSON – Karthikeyan Sep 11 '18 at 05:36
  • @Karthikeyan, if you look at the code, i am also not getting params in `newInstance` i am getting and storing them in bundle to get them in `onCreate()`, thats the snipet from the fragment that is mostly used and considered best practice, again you can do that, but i think its better way of doing it – Abdul Kawee Sep 11 '18 at 05:42
  • still i am getting same error but this time in main.kt file " override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)................................" – Karthikeyan Sep 11 '18 at 09:14
  • Whats the error can you share it in question @Karthikeyan – Abdul Kawee Sep 11 '18 at 09:15
  • @Karthikeyan please check edited answer, you have to edit fragment constructor as like this, – Abdul Kawee Sep 11 '18 at 09:37
  • NOO, bcz i need to pass the args here " recyclerView.adapter = TransactionRecyclerAdapter(transactionList,appDatabase,direction,networkDefinitionProvider)" then how to do without args . . .??? – Karthikeyan Sep 11 '18 at 09:59
  • you are passing args in `newInstance()` why do you need to pas it to fragment constructor – Abdul Kawee Sep 11 '18 at 10:00
  • but when i remove this i got error . .it does not work. here also error : ."ReceiveFragment(myIT, myAppDatabase, myIncoming, myNetwork,bundle).apply {" – Karthikeyan Sep 11 '18 at 10:07
  • @Karthikeyan you have to make empty constructor, then get the values through `newInstance` then pass them in adapter – Abdul Kawee Sep 11 '18 at 15:51
  • How to do that ?? plz – Karthikeyan Sep 14 '18 at 04:24
  • @Karthikeyan please check 2nd last line, it has how to use empty constructor – Abdul Kawee Sep 14 '18 at 04:27
  • @Karthikeyan please check this, i have created gist for you https://gist.github.com/mak12/cd3c6570b4902d43e6746d3c0340724c – Abdul Kawee Sep 14 '18 at 04:29
  • last2nd line means ?? – Karthikeyan Sep 14 '18 at 04:46
  • nevermind, please check the link, it has complete code of fragment – Abdul Kawee Sep 14 '18 at 04:50
  • @Karthikeyan did it help you? – Abdul Kawee Sep 14 '18 at 05:49
1

As mentioned here,

If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.

So please change your code like this

class ReceiveFragment : Fragment{

   private val transactionList: List<TransactionEntity>? = null
   val appDatabase: AppDatabase? = null
   private val direction: TransactionAdapterDirection? = null
   val networkDefinitionProvider: NetworkDefinitionProvider? = null

   //Add empty constructor to avoid the exception
   constructor() : super()

   constructor(transactionList: List<TransactionEntity>, appDatabase: AppDatabase, direction: TransactionAdapterDirection, networkDefinitionProvider: NetworkDefinitionProvider){
      this.transactionList = transactionList
      this.appDatabase = appDatabase
      this.direction = direction
      this.networkDefinitionProvider = networkDefinitionProvider
   }
}