Search code examples
androidandroid-layoutandroid-fragmentskotlinandroid-custom-view

InstantiationException with my customCalendarView


I'm currently running into an error where my custom calendar view will not run on my fragment layout. For some reason, I run into this InstantiationException error, "java.lang.InstantiationException: The layout involves creation of com.example.road2success.CustomCalendarView over 100 levels deep. Infinite recursion?" And this is what the actually error says:

java.lang.InstantiationException: The layout involves creation of com.example.road2success.CustomCalendarView over 100 levels deep. Infinite recursion?
at org.jetbrains.android.uipreview.ViewLoader.loadClass(ViewLoader.java:181)
at org.jetbrains.android.uipreview.ViewLoader.loadView(ViewLoader.java:144)
at com.android.tools.idea.rendering.LayoutlibCallbackImpl.loadView(LayoutlibCallbackImpl.java:309)
at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:418)
at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:429)
at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:333)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
at android.view.LayoutInflater.rInflate_Original(LayoutInflater.java:863)
at android.view.LayoutInflater_Delegate.rInflate(LayoutInflater_Delegate.java:72)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:837)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
at com.example.road2success.CustomCalendarView.<init>(CustomCalendarView.kt:44)
at sun.reflect.GeneratedConstructorAccessor130.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.jetbrains.android.uipreview.ViewLoader.createNewInstance(ViewLoader.java:403)
at org.jetbrains.android.uipreview.ViewLoader.loadClass(ViewLoader.java:186)
at org.jetbrains.android.uipreview.ViewLoader.loadView(ViewLoader.java:144)
at com.android.tools.idea.rendering.LayoutlibCallbackImpl.loadView(LayoutlibCallbackImpl.java:309)
at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:418)
at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:429)
at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:333)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
at android.view.LayoutInflater.rInflate_Original(LayoutInflater.java:863)
at android.view.LayoutInflater_Delegate.rInflate(LayoutInflater_Delegate.java:72)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:837)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
at com.example.road2success.CustomCalendarView.<init>(CustomCalendarView.kt:44)
at sun.reflect.GeneratedConstructorAccessor130.newInstance(Unknown Source)

(couldn't add the rest but it just repeats this over and over)

I've tried many different things to stop the infinite recursion but I'm not sure what is actually causing this, so if anybody could possibly help me figure it out that would be great. Also line 44 in the error statement refers to the view instantiation(var view).

CustomCalendarView.kt

package com.example.road2success

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.util.AttributeSet
import android.util.Log.d
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.annotation.Nullable
import androidx.core.content.getSystemService
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList


class CustomCalendarView: LinearLayout{
    constructor(context: Context?):super(context)
    constructor(context: Context?, @Nullable attr: AttributeSet?):super(context, attr)
    var stuck = 1
    private var nextButton: ImageButton? = null
    private var prevButton: ImageButton? = null
    private var currentDate: TextView? = null
    private var grid: GridView? = null
    val maxDays = 42
    var calendar: Calendar = Calendar.getInstance(Locale.ENGLISH)
    var dates: ArrayList<Date> = ArrayList()
    var eventList: ArrayList<Events> = ArrayList()
    var dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM yyyy", Locale.ENGLISH)
    var monthFormat: SimpleDateFormat = SimpleDateFormat("MMMM", Locale.ENGLISH)
    var yearFormat: SimpleDateFormat = SimpleDateFormat("yyyy", Locale.ENGLISH)
    var eventFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
    var myGrid: MyGridAdapter? = null

    init {
            var inflater=
                context!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            var view= inflater.inflate(R.layout.frag_home, this,true)
            nextButton = view.findViewById(R.id.rightButton)
            prevButton = view.findViewById(R.id.leftButton)
            currentDate = view.findViewById(R.id.dateText)
            grid = view.findViewById(R.id.calendar)
            prevButton?.setOnClickListener {
                calendar.add(Calendar.MONTH, -1)
                setUpCalendar()
            }
            nextButton?.setOnClickListener {
                calendar.add(Calendar.MONTH, 1)
                setUpCalendar()
            }
            grid?.setOnItemClickListener { adapterView: AdapterView<*>, view1: View, i: Int, l: Long ->
                var builder: AlertDialog.Builder = AlertDialog.Builder(context)
                builder.setCancelable(true)
                var addView: View =
                    LayoutInflater.from(context).inflate(R.layout.create_a_journey, null)
                var stages: Spinner = addView.findViewById(R.id.interview)
                var stageOther: EditText = addView.findViewById(R.id.stageother)
                var dText: EditText = addView.findViewById(R.id.dateText)
                var day = eventFormat.format(dates.get(i))
                var month = monthFormat.format(dates.get(i))
                var year = yearFormat.format(dates.get(i))
                dText.setText(day + "/" + month + "/" + year)
                var cText: EditText = addView.findViewById(R.id.company)
                var add: ImageButton = addView.findViewById(R.id.addButton)
                var cancel: ImageButton = addView.findViewById(R.id.cancelButton)
                var time: TimePicker = addView.findViewById(R.id.timeClock)
                var timeSet = EditText(context)
                var myAdapter: ArrayAdapter<String> = ArrayAdapter(
                    context,
                    android.R.layout.simple_list_item_1,
                    resources.getStringArray(R.array.stages)
                )
                myAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
                stages.adapter = myAdapter
                time.setOnTimeChangedListener { view, hourOfDay, minute ->
                    timeSet.setText(hourOfDay.toString() + ":" + minute.toString())
                }
                stages?.setOnClickListener {
                    stageOther.isVisible = stages?.selectedItem.equals("Other")
                }
                add?.setOnClickListener {
                    if (cText.text.isEmpty() || stages.selectedItem.equals(null)) {
                        Toast.makeText(
                            this.context,
                            "WARNING: Please fill in the stage and the company!",
                            Toast.LENGTH_LONG
                        ).show()
                    } else {
                        if (stages?.selectedItem.equals("Other")) {
                            saveEvent(
                                cText.toString(),
                                stageOther.toString(),
                                timeSet.toString(),
                                dText.toString(),
                                month.toString(),
                                year.toString()
                            )
                        } else {
                            saveEvent(
                                cText.toString(),
                                stages.selectedItem.toString(),
                                timeSet.toString(),
                                dText.toString(),
                                month.toString(),
                                year.toString()
                            )
                        }
                    }
                }
                cancel?.setOnClickListener {
                    Toast.makeText(this.context, "Canceled", Toast.LENGTH_LONG).show()
                }
                builder.setView(addView)
                builder.create()
                builder.show()
            }
            grid?.setOnItemLongClickListener { parent, view, position, id ->
                var date = eventFormat.format(dates.get(position))
                var builder: AlertDialog.Builder = AlertDialog.Builder(context)
                builder.setCancelable(true)
                var addView: View =
                    LayoutInflater.from(context).inflate(R.layout.showjourneys, null)
                var EventT: RecyclerView = addView.findViewById(R.id.eventsBoard)
                var layout: RecyclerView.LayoutManager = LinearLayoutManager(addView.context)
                EventT.layoutManager = layout
                EventT.setHasFixedSize(true)
                var EventTA = EventRecyclerView(CollectJourneys(date))
                EventT.adapter = EventTA
                EventTA.notifyDataSetChanged()

                builder.setView(addView)
                builder.create()
                builder.show()
                return@setOnItemLongClickListener true
            }
    }

    private fun CollectJourneys(date: String?): ArrayList<Events>{
        var aList: ArrayList<Events> = ArrayList()
        var db = DBHelper(context)
        var database: SQLiteDatabase = db.readableDatabase
        var cursor: Cursor? = db.ReadEvents(date,database)
        while(cursor?.moveToNext()!!){
            var company = cursor.getString(cursor.getColumnIndex(DBStructure.company))
            var stage = cursor.getString(cursor.getColumnIndex(DBStructure.stage))
            var time = cursor.getString(cursor.getColumnIndex(DBStructure.time))
            var date = cursor.getString(cursor.getColumnIndex(DBStructure.date))
            var month = cursor.getString(cursor.getColumnIndex(DBStructure.month))
            var year = cursor.getString(cursor.getColumnIndex(DBStructure.year))
            var event = Events(company,stage,time,date,month,year)
            aList.add(event)
        }
        cursor.close()
        database.close()
        return aList
    }


    private fun saveEvent(company:String?,stage:String?,time:String?, date:String?, month:String?, year:String?){
        var db = DBHelper(context)
        var database: SQLiteDatabase = db.writableDatabase
        db.saveEvent(company,stage,time,date,month,year,database)
        db.close()
        Toast.makeText(context,"Event Added", Toast.LENGTH_LONG).show()
    }
    private fun setUpCalendar() {
        var selectedDate = dateFormat.format(calendar.time)
        currentDate?.setText(selectedDate)
        dates.clear()
        var monthCalendar: Calendar = calendar.clone() as Calendar
        monthCalendar.set(Calendar.DAY_OF_MONTH,1)
        var firstDayOfMonth = monthCalendar.get(Calendar.DAY_OF_WEEK)-1
        monthCalendar.add(Calendar.DAY_OF_MONTH,-firstDayOfMonth)
        CollectEventsPerMonth(monthFormat.format(calendar.time),yearFormat.format(calendar.time))

        while(dates.size < maxDays){
            dates.add(monthCalendar.time)
            monthCalendar.add(Calendar.DAY_OF_MONTH,1)
        }
        myGrid = MyGridAdapter(context,dates,calendar,eventList)
        grid?.adapter = myGrid
    }
    private fun CollectEventsPerMonth(month: String?,year: String?){
        var db = DBHelper(context)
        var database: SQLiteDatabase = db.readableDatabase
        var cursor: Cursor? = db.ReadEventsPerMonth(year,month,database)
        while(cursor?.moveToNext()!!){
            var company = cursor.getString(cursor.getColumnIndex(DBStructure.company))
            var stage = cursor.getString(cursor.getColumnIndex(DBStructure.stage))
            var time = cursor.getString(cursor.getColumnIndex(DBStructure.time))
            var date = cursor.getString(cursor.getColumnIndex(DBStructure.date))
            var month = cursor.getString(cursor.getColumnIndex(DBStructure.month))
            var year = cursor.getString(cursor.getColumnIndex(DBStructure.year))
            var event = Events(company,stage,time,date,month,year)
            eventList.add(event)
        }
        cursor.close()
        database.close()
    }

}

frag_home.xml:

    <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.road2success.CustomCalendarView
        android:id="@+id/customCalView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="28dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

HomeFragment.kt:

package com.example.road2success.home

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.example.road2success.CustomCalendarView
import com.example.road2success.R

class HomeFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.frag_home, container, false)
        val customCal: CustomCalendarView = root.findViewById(R.id.customCalView)

        return root
    }

}

MainActivity.kt(if needed):

package com.example.road2success

import android.os.Bundle
import android.view.Menu
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar: Toolbar = findViewById(R.id.toolBar)
        setSupportActionBar(toolbar)
        val drawerLayout: DrawerLayout = findViewById(R.id.drawMain)
        val navView: NavigationView = findViewById(R.id.NavView)
        val navController = findNavController(R.id.nav_host_fragment)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_home_day, R.id.nav_home_month,
                R.id.nav_home_week, R.id.nav_home_year, R.id.nav_settings,
                R.id.nav_stats, R.id.nav_create), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

Again any help is appreciated and this is my first post so sorry for any mistakes.


Solution

  • Your custom View is inflating a layout that contains an instance of your custom View. So:

    • You create an instance of your custom View (instance #1)
    • Instance #1 inflates a layout which creates an instance of your custom View (instance #2)
    • Instance #2 inflates a layout which creates an instance of your custom View (instance #3)
    • Instance #3 inflates a layout which creates an instance of your custom View (instance #4)
    • And so on

    Since your layout is named frag_home, and since it does not contain any of the widgets that your custom View expects, my guess is that you are inflating the wrong layout.