#android #android-fragments #android-viewpager #kotlin-coroutines
#Android #android-фрагменты #android-viewpager #kotlin-сопрограммы
Вопрос:
У меня есть три фрагмента для действия. На одном я загружаю представление по умолчанию, которое должно быть заполнено данными из Календаря 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()
}
}
Ответ №1:
Проблема в GlobalScope
том, что . Когда вы удаляетесь от фрагмента / действия, его представления уничтожаются, но GlobalScope
ограничивают задачу жизненным циклом приложения, поэтому она не уничтожается, и последующее обращение к любым представлениям приведет к сбою приложения с помощью NullPointerException
.
Поэтому вместо этого привяжите фоновые действия к жизненному циклу фрагмента с помощью lifecycleOwner.lifecycleScope
или lifecycle.coroutineScope
. ознакомьтесь с официальными документами для получения дополнительной информации.
Комментарии:
1. спасибо rcs! в этом так много смысла, есть дополнительный вопрос… в жизненном цикле моя синхронизация будет отменена при переходе к различному фрагменту / действию… таким образом, данные календаря Google не будут импортированы … это не было бы отличным пользовательским интерфейсом, учитывая, что пользователь уже дал разрешение… как мне запустить синхронизацию без активного представления?
2. попробуйте сохранить данные локально, используя базу данных room, пусть recyclerview зависит от локальных данных для отображения содержимого и использует activitylifecycle или область viewmodel, ограниченную действием, для запуска фоновой задачи, поскольку
global scope
не рекомендуется использовать.