RecyclerView 不能为 null - 当异步任务加载到一个碎片上而另一个碎片上时
RecycleView cannot be null - when asynchornous task is loading on one frag while on another frag
我在 activity 上有三个片段。在一个上,我正在加载一个默认视图,该视图将由 Google 日历中的数据填充,但异步 -syncwholeCalendar()。当应用程序加载默认视图时,应用程序崩溃时,正在加载日历数据并且用户位于 activity 中的另一个片段 - 说 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()
}
}
问题出在 GlobalScope
。当您离开 fragment/activity 时,它的视图会被销毁,但 GlobalScope
将任务绑定到应用程序生命周期,因此,它不会被销毁,稍后引用任何视图将导致应用程序失败并显示 NullPointerException
.
因此,改为使用 lifecycleOwner.lifecycleScope
或 lifecycle.coroutineScope
将后台活动绑定到片段生命周期。查看 official docs 了解更多信息。
我在 activity 上有三个片段。在一个上,我正在加载一个默认视图,该视图将由 Google 日历中的数据填充,但异步 -syncwholeCalendar()。当应用程序加载默认视图时,应用程序崩溃时,正在加载日历数据并且用户位于 activity 中的另一个片段 - 说 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()
}
}
问题出在 GlobalScope
。当您离开 fragment/activity 时,它的视图会被销毁,但 GlobalScope
将任务绑定到应用程序生命周期,因此,它不会被销毁,稍后引用任何视图将导致应用程序失败并显示 NullPointerException
.
因此,改为使用 lifecycleOwner.lifecycleScope
或 lifecycle.coroutineScope
将后台活动绑定到片段生命周期。查看 official docs 了解更多信息。