从单一实例调用片段方法



在我的Android应用程序中,我有一个专门处理数据库操作的Singleton(kotlin object(。在我的主要活动中,我设置了一个带有自定义片段的ViewPager,创建了一个多窗格布局。

其中一个片段包含一个RecyclerView,当数据库 Singleton 检测到数据更改时,需要更新该。

我的逻辑是在我的数据库 Singleton 中创建interface,并使片段实现该接口。但我无法做到这一点。我收到此错误:

java.lang.ExceptionInInitializerError
        at com.jagoancoding.foodstalladmin.MainActivity.onCreate(MainActivity.kt:18)
        at android.app.Activity.performCreate(Activity.java:5933)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2282)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2391)
        at android.app.ActivityThread.access$900(ActivityThread.java:147)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1296)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5256)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:898)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference
        at com.google.android.gms.internal.zzejp.hashCode(Unknown Source)
        at java.util.Collections.secondaryHash(Collections.java:3405)
        at java.util.HashMap.get(HashMap.java:300)
        at com.google.android.gms.internal.zzeju.zzi(Unknown Source)
        at com.google.firebase.database.Query.zzb(Unknown Source)
        at com.google.firebase.database.Query.addValueEventListener(Unknown Source)
        at com.jagoancoding.foodstalladmin.DbUtil.<clinit>(DbUtil.kt:22)

这是我的单例:

object DbUtil : DatabaseListener {
    const val TAG = "DatabaseUtil"
    lateinit var itemsListener: ItemsListener
    interface ItemsListener {
        fun onItemsChange(items: ArrayList<Item>)
    }
    val rootRef: DatabaseReference = FirebaseDatabase.getInstance().reference
    val shopRef: DatabaseReference = rootRef.child(DataUtil.ROOT_SHOP).apply {
        addValueEventListener(readFoodItemsListener)
    }
    val usersRef: DatabaseReference = rootRef.child(DataUtil.ROOT_USERS)
    // Automatically update local list when data is changed upstream
    private val readFoodItemsListener = object : ValueEventListener {
        override fun onDataChange(p0: DataSnapshot?) {
            if (p0!!.exists()) {
                p0.children.mapNotNullTo(DataUtil.allFoodItems) { it.getValue(Item::class.java) }
                //TODO: Update Items list in fragment
                itemsListener.onItemsChange(DataUtil.allFoodItems)
            }
        }
        override fun onCancelled(p0: DatabaseError?) {
            Log.e(TAG, "shopRef:onCancelled: ", p0?.toException())
        }
    }
    override fun getItems(dataRoot: String) {
        // Get Database food items to local list
        shopRef.addListenerForSingleValueEvent(readFoodItemsListener)
    }
    override fun onItemAdd(item: Item) {
        // Set up the unique ID
        val itemId = shopRef.child(item.name).push().key
        shopRef.child(itemId).setValue(Item(itemId, item.name, item.price))
    }
    override fun onItemUpdate(item: Item, name: String, price: Double) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
    override fun onItemDelete(item: Item) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
    // ... other methods ...
}

我的片段:

class ItemsPage : Fragment(), DbUtil.ItemsListener {
    val LOG_TAG = "ItemsPageFragment"
    private lateinit var itemsRV: RecyclerView
    private lateinit var itemFAB: FloatingActionButton
    private lateinit var loadPB: ProgressBar
    private lateinit var mAdapter: ItemsAdapter
    private var items = arrayListOf<Item>()
    override fun onAttach(context: Context?) {
        super.onAttach(context)
        DbUtil.itemsListener = this
    }
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val rootView = inflater.inflate(R.layout.items_page, container, false)
        mAdapter = ItemsAdapter(items)
        itemsRV = rootView.findViewById<RecyclerView>(R.id.rv_items).apply {
            adapter = mAdapter
            layoutManager = LinearLayoutManager(rootView.context)
        }
        itemFAB = rootView.findViewById<FloatingActionButton>(R.id.fab_add_item).apply {
            setOnClickListener { DbUtil.onItemAdd(DataUtil.demoFood[0]) }
        }
        loadPB = rootView.findViewById<ProgressBar>(R.id.pb_items).apply {
            visibility = View.GONE
        }
        return rootView
    }
    override fun onItemsChange(items: ArrayList<Item>) {
        loadPB.visibility = View.VISIBLE
        this.items = items
        mAdapter.notifyItemRangeChanged(0, items.size - 1)
        loadPB.visibility = View.GONE
    }
}

我的活动:

class MainActivity : AppCompatActivity() {
    val TAG = "MainActivity"
    // Fragments
    private lateinit var mPager: ViewPager
    private lateinit var mAdapter: PageAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        DbUtil.getItems("shop")
        mAdapter = PageAdapter(supportFragmentManager)
        mPager = findViewById<ViewPager>(R.id.vp_main).apply { adapter = mAdapter }
    }
}

我尝试在我的活动上实现接口并以这种方式调用我的 Fragment 方法,但我遇到了与上面相同的错误。

我也阅读了这个答案,并试图用我的单例和片段进行设置,但这解决了同样的问题。

我找到了解决方案。

这与调用DbUtil.getItems()时未初始化itemsListener有关,它需要一个非空ItemsListener来调用其接口方法 - onItemsChange()

解决方案是在调用DbUtil.getItems之前设置DbUtil.itemsListener。我更新了片段中的onAttach()方法:

override fun onAttach(context: Context?) {
    super.onAttach(context)
    DbUtil.itemsListener = this
    DbUtil.getItems()
}

最新更新