首页 > 解决方案 > RecycleView 不能为空 - 当异步任务加载在一个片段上而另一个片段上时

问题描述

我有一个活动的三个片段。一方面,我正在加载一个默认视图,该视图将由来自 Google 日历的数据填充,但异步 -syncwholeCalendar()。当应用程序加载默认视图时,应用程序崩溃时,日历数据正在加载并且用户在活动中的另一个片段上 - 说 recyclerview 不能为空。同样是问题,当我在不等待同步完成的情况下注销时......问题是getEvents(),当调用recyclerview在同步时对其进行充气......

是否有我遗漏的步骤或我采取的方法不正确?

class Home : Fragment() {
companion object {
    @JvmStatic
    fun start(context: Context) {
        val starter = Intent(context, Home::class.java)
        context.startActivity(starter)
    }

    private const val RC_SIGN_IN = 9001
    private val transport = AndroidHttp.newCompatibleTransport()
    private val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()
}

var usersReference: DatabaseReference? = null
var firebaseUser: FirebaseUser? = null
private var fabExpanded = false

//Calendar
private lateinit var mService: Calendar
private lateinit var calendar: java.util.Calendar
private lateinit var progressBar: ProgressDialog
private lateinit var firestore: FirebaseFirestore
private var isSynced = false
private var isFirstTime = false
private lateinit var googleCredential: GoogleAccountCredential
private var googleSignInAccount: GoogleSignInAccount? = null


override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val view: View = inflater.inflate(R.layout.fragment_home, container, false)



firebaseUser = FirebaseAuth.getInstance().currentUser
    val userId = FirebaseAuth.getInstance().currentUser!!.uid
    firestore = FirebaseFirestore.getInstance()


progressBar = ProgressDialog(activity)
    progressBar.setCancelable(false)
    progressBar.setMessage("Loading...")
    calendar = java.util.Calendar.getInstance()
    isFirstTime = this.requireActivity().getSharedPreferences("PRE", MODE_PRIVATE).getBoolean(
        "first",
        true
    )

    usersReference =
        FirebaseDatabase.getInstance().reference.child("Users").child(firebaseUser!!.uid)
    usersReference!!.addValueEventListener(object : ValueEventListener {

        override fun onDataChange(p0: DataSnapshot) {

            if (p0.exists()) {
                val user: Users? = p0.getValue(Users::class.java)

                if (context != null) {
                    view.calEvenName.text = user!!.getFirstName()
                    view.calMornName.text = user.getFirstName()
                }
            }
        }

        override fun onCancelled(p0: DatabaseError) {

        }
    })

    return view
}

private fun showCalendar() {
    val today = java.util.Calendar.getInstance()
    val datePicker = DatePickerDialog(
        requireActivity(), R.style.DateTimePickerTheme,
        { datePicker: DatePicker, year: Int, month: Int, day: Int ->
            calendar.set(year, month, day)
            getCredentials()
        },
        calendar.get(java.util.Calendar.YEAR),
        calendar.get(java.util.Calendar.MONTH),
        calendar.get(
            java.util.Calendar.DAY_OF_MONTH
        )
    )

//        datePicker.datePicker.minDate = today.timeInMillis
        datePicker.show()
    }

//closes FAB submenus
private fun closeSubMenusFab() {
    layoutFabNote.visibility = View.INVISIBLE
    layoutfabEvent?.visibility = View.INVISIBLE
    layoutfabMsg?.visibility = View.INVISIBLE
    fabbackground.setBackgroundColor(Color.TRANSPARENT)
    home_fab.setImageResource(R.drawable.ic_baseline_fab_24)
    fabExpanded = false
}

//Opens FAB submenus
private fun openSubMenusFab() {
    layoutfabEvent?.visibility = View.VISIBLE
    layoutfabMsg?.visibility = View.VISIBLE
    layoutFabNote?.visibility = View.VISIBLE
    fabbackground.setBackgroundColor(Color.parseColor("#E0F7FA"))

    //Change settings icon to 'X' icon
    home_fab.setImageResource(R.drawable.ic_baseline_close_24)
    fabExpanded = true
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {


    //Only main FAB is visible in the beginning
    closeSubMenusFab()

    //Agenda Calendar Controls/Links
    homeCalendarDate.setOnClickListener { showCalendar() }

    rightCalArrow.setOnClickListener {
        calendar.add(java.util.Calendar.DAY_OF_MONTH, 1)
        getCredentials()
    }

    leftCalArrow.setOnClickListener {
        calendar.add(java.util.Calendar.DAY_OF_MONTH, -1)
        getCredentials()
    }

    //ResetDate Button
    resetIcon.setOnClickListener {
        //   Toast.makeText(activity,"Reset Button Clicked", Toast.LENGTH_LONG).show()
        calendar = java.util.Calendar.getInstance()
        getCredentials()
    }

    //FAB Buttons
    home_fab.setOnClickListener(View.OnClickListener {
        if (fabExpanded) {
            closeSubMenusFab()
        } else {
            openSubMenusFab()
        }
    })

    fabMsg.setOnClickListener {
        val intent = Intent(activity, MyContacts::class.java)
        activity?.startActivity(intent)
    }

    fabEvent.setOnClickListener {
        val intent = Intent(activity, AddEvent::class.java)
        activity?.startActivity(intent)
    }

    fabNote.setOnClickListener {
        val intent = Intent(activity, EditNote::class.java)
        activity?.startActivity(intent)
    }

    //No Event cal display
    homecreateEvent.setOnClickListener {
        val intent = Intent(activity, AddEvent::class.java)
        activity?.startActivity(intent)
    }

    homesendMessage.setOnClickListener {
        val intent = Intent(activity, MyContacts::class.java)
        activity?.startActivity(intent)
    }

}

private fun getCredentials() {
    val date = SimpleDateFormat("E, MMM dd, yyyy", Locale.getDefault()).format(calendar.time)
    homeCalendarDate.text = date
//        progressBar.show()
        googleSignInAccount = GoogleSignIn.getLastSignedInAccount(activity)
        googleCredential = GoogleAccountCredential.usingOAuth2(
            activity,
            listOf(CalendarScopes.CALENDAR)
        )
            .setBackOff(ExponentialBackOff())
            .setSelectedAccountName(googleSignInAccount?.account?.name)

    googleCredential.selectedAccountName = googleSignInAccount?.account?.name
    mService = Calendar.Builder(
        transport, jsonFactory, googleCredential
    )
        .setApplicationName(getString(R.string.app_name))
        .build()

    if (!isSynced) {
        Log.d(TAG, "getCredentials: syncing data")
        if (!isFirstTime) {
            getEvents()
        }
        syncWholeCalendar()
    } else {
        getEvents()
    }
}

private fun getEvents() {
    val noEvents = view?.findViewById<LinearLayout>(R.id.no_events)
    val homeAgenda = view?.findViewById<RecyclerView>(R.id.homeAgenda)
    GlobalScope.launch {
        Log.d(
            TAG,
            "Time current: ${calendar.get(java.util.Calendar.DAY_OF_MONTH)}, ${calendar.get(java.util.Calendar.HOUR_OF_DAY)}, ${
                calendar.get(java.util.Calendar.MINUTE)
            }"
        )
        calendar.set(java.util.Calendar.HOUR_OF_DAY, 0)
        calendar.set(java.util.Calendar.MINUTE, 0)
        calendar.set(java.util.Calendar.SECOND, 0)
        calendar.set(java.util.Calendar.MILLISECOND, 0)

        val db = AppDatabase.getInstance(requireActivity())
        val events = db.getEvents(calendar.timeInMillis)
        val listOfEvents = ArrayList<MyEvent>()
        for (event in events) {
            if (event.isPrimary) {
                listOfEvents.add(event)
            } else {
                listOfEvents.add(0, event)
            }
        }

        Log.d(TAG, "getEvents: ${events.size}")

        requireActivity().runOnUiThread {
            try {
                **val recyclerView: RecyclerView? = homeAgenda
                recyclerView!!.layoutManager = LinearLayoutManager(activity)
                recyclerView.adapter = EventsAdapter(**
                    requireContext(), listOfEvents, calendar.timeInMillis
                )
                if (listOfEvents.size != 0) {
                    noEvents!!.visibility = View.GONE
                } else {
                    noEvents!!.visibility = View.VISIBLE
                }
            } catch (e: UserRecoverableAuthIOException) {
                requireActivity().runOnUiThread { progressBar.dismiss() }
                startActivityForResult(e.intent, RC_SIGN_IN)
            } catch (e: IOException) {
                requireActivity().runOnUiThread { progressBar.dismiss() }
                e.printStackTrace()
            }
        }
    }
}

private fun syncWholeCalendar() {
    GlobalScope.launch {
        try {
            val calendars = mService.CalendarList().list().execute()
            val calendarsList = calendars.items
            for (cal in calendarsList) {
                val events = mService.events().list(cal.id)
                    .setOrderBy("startTime")
                    .setSingleEvents(true)
                    .execute()

                // .setTimeMin(dateFrom)
                val eventsList = events.items
                for (event in eventsList) {
                    event.isLocked = !cal.isPrimary
                    FirebaseFirestore.getInstance().collection("Users")
                        .document(FirebaseAuth.getInstance().currentUser!!.uid)
                        .collection("events")
                        .document(event.id).set(event)
                    var startTimeInMilli: Long = java.util.Calendar.getInstance().timeInMillis
                    if (event.start.dateTime != null) {
                        startTimeInMilli = event.start.dateTime.value
                    } else if (event.start.date != null) {
                        startTimeInMilli = getAbsoluteDate(event.start.date.value)
                    }

                    var endTimeInMilli = java.util.Calendar.getInstance().timeInMillis

                    if (event.end.dateTime != null) {
                        endTimeInMilli = event.end.dateTime.value
                    } else if (event.end.date != null) {
                        endTimeInMilli = getAbsoluteDate(event.start.date.value)
                        Log.d(
                            TAG,
                            "syncWholeCalendar:${event.summary}, ${event.start.date} ${event.end.date}"
                        )
                    }
                    val db = AppDatabase.getInstance(requireActivity())
                    val startTime = getAbsoluteDate(startTimeInMilli)
                    val endTime = getAbsoluteDate(endTimeInMilli)
                    db.insert(
                        MyEvent(
                            event.id,
                            event.summary,
                            event.description,
                            null,
                            null,
                            startTimeInMilli,
                            endTimeInMilli,
                            cal.isPrimary,
                            true,
                            startTime,
                            endTime,
                            event.location,
                            null,
                            null
                        )
                    )
                    Log.d(
                        TAG,
                        "syncWholeCalendar: ${event.start.dateTime}, ${event.start.date}"
                    )
                }
            }
            isSynced = true
            requireActivity().getSharedPreferences("PRE", MODE_PRIVATE)
                .edit().putBoolean("first", false).apply()
            getEvents()

            Log.d(TAG, "syncWholeCalendar: Data Synced Successfully")
        } catch (e: UserRecoverableAuthIOException) {
            requireActivity().runOnUiThread { progressBar.dismiss() }
            startActivityForResult(e.intent, RC_SIGN_IN)
        } catch (e: IOException) {
            requireActivity().runOnUiThread { progressBar.dismiss() }
            e.printStackTrace()
        }
    }
}

override fun onPause() {
    super.onPause()
    closeSubMenusFab()
}

override fun onResume() {
    super.onResume()
    getCredentials()
}

}

标签: androidandroid-fragmentsandroid-viewpagerkotlin-coroutines

解决方案


问题出在GlobalScope. 当您离开片段/活动时,它的视图会被销毁,但GlobalScope会将任务绑定到应用程序生命周期,因此,它不会被销毁,并且稍后引用任何视图将导致应用程序失败并显示NullPointerException.

因此,请使用lifecycleOwner.lifecycleScopeor将后台活动绑定到片段生命周期lifecycle.coroutineScope。查看官方文档了解更多信息。


推荐阅读