I have the following code, I would like to use async-await for the two calls.
At the moment inside the function I used a kind of check variable, which when both are set the code is executed.
How can I do?
....
.onAppear {
DispatchQueue.global(qos: .background).async {
getRemoteHead(url: repoUrlStr)
getRemoteBranch(url: repoUrlStr)
}
}
func getRemoteHead(url: String) {
do {
let branch = try Remote().getRemoteHEAD(url: url)
if branch[0].contains("fatal:") {
Log.warning("Error: getRemoteHead")
activeSheet = .error("Error: getRemoteHead")
} else {
self.mainBranch = branch[0]
self.selectedBranch = branch[0]
self.check += 1
if check == 2 {
check = 0
activeSheet = .select
}
}
} catch {
Log.error("Failed to find main branch name.")
}
}
func getRemoteBranch(url: String) {
do {
let branches = try Remote().getRemoteBranch(url: url)
if branches[0].contains("fatal:") {
Log.warning("Error: getRemoteBranch")
activeSheet = .error("Error: getRemoteBranch")
} else {
self.arrayBranch = branches
self.check += 1
if check == 2 {
check = 0
activeSheet = .select
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// Force a UI Update.
allBranches.toggle()
}
}
}
} catch {
Log.error("Failed to find branches.")
}
}
I'm trying to get the context of my Service in order. The service opens up an overlay that draws on other apps. The overlay comes up but if I interact with any of the views, the app crashes and gives this error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
Here is the full error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1068)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109)
at android.app.Dialog.show(Dialog.java:340)
at android.widget.Spinner$DialogPopup.show(Spinner.java:1146)
at android.widget.Spinner.performClick(Spinner.java:792)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
From what I've been able to determine through Google Search and SO is that the issue is with the context. Below is the code for my Service.
class Dooa: Service() {
private lateinit var floatView: ViewGroup
private lateinit var floatWindowLayoutParams: WindowManager.LayoutParams
private var LAYOUT_TYPE: Int? = null
private lateinit var windowManager: WindowManager
private lateinit var spinnerAccount: Spinner
private lateinit var tvDateAT: TextView
private lateinit var spinnerType: Spinner
private lateinit var etTitle: EditText
private lateinit var etMemo: EditText
private lateinit var spinnerCategory: Spinner
private lateinit var spinnerDebitOrCredit: Spinner
private lateinit var etAmount: EditText
private lateinit var ibSave: ImageButton
private lateinit var ibCancel: ImageButton
private var account: String = "Joint"
private var debitOrCredit: String = "Debit"
private var category: String = ""
private var type: String = "CC"
private var mils: Long = 0
private var balance: String = ""
private var context: Context? = null
private lateinit var db : FirebaseFirestore
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
context = MyApp().getContext()
Log.d("blocks", "context: $context")
if (intent != null) {
if (intent.action == START) {
db = FirebaseFirestore.getInstance()
val metrics = applicationContext.resources.displayMetrics
val width = metrics.widthPixels
val height = metrics.heightPixels
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val inflator = this.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
floatView = inflator.inflate(R.layout.dooa_transaction_card, null) as ViewGroup
spinnerAccount = floatView.findViewById(R.id.spinnerAccount)
tvDateAT = floatView.findViewById(R.id.tvDateAT)
spinnerType = floatView.findViewById(R.id.spinnerType)
etTitle = floatView.findViewById(R.id.etTitle)
etMemo = floatView.findViewById(R.id.etMemo)
spinnerCategory = floatView.findViewById(R.id.spinnerCategory)
spinnerDebitOrCredit = floatView.findViewById(R.id.spinnerDebitOrCredit)
etAmount = floatView.findViewById(R.id.etAmount)
ibSave = floatView.findViewById(R.id.ibSave)
ibCancel = floatView.findViewById(R.id.ibCancel)
//ACCOUNT SPINNER
ArrayAdapter.createFromResource(
this,
R.array.accounts,
R.layout.spinner_item
).also { adapter ->
Log.d("blocks", "AS ArrayAdapter ran")
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerAccount.adapter = adapter
spinnerAccount.setSelection(0)
}
spinnerAccount.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
account = selection.toString()
getDB()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
tvDateAT.setOnClickListener {
val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(
this#Dooa,
{ view, year, monthOfYear, dayOfMonth ->
// Save milliseconds for date picked.
mils = c.timeInMillis
val m = monthOfYear + 1
// Display Selected date in textbox
tvDateAT.text = getString(
R.string.date_picked,
m.toString(),
dayOfMonth.toString(),
year.toString()
)
},
year,
month,
day
)
dpd.show()
}
//TYPE
ArrayAdapter.createFromResource(
this,
R.array.type,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerType.adapter = adapter
spinnerType.setSelection(0)
}
spinnerType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
type = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
etTitle.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
etTitle.isCursorVisible = true
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
updatedFloatParamsFlag.flags = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
//CATEGORY
ArrayAdapter.createFromResource(
this,
R.array.category,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerCategory.adapter = adapter
spinnerCategory.setSelection(0)
}
spinnerCategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
category = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
//DEBIT OR CREDIT
ArrayAdapter.createFromResource(
this,
R.array.debit_or_credit,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerDebitOrCredit.adapter = adapter
spinnerDebitOrCredit.setSelection(0)
}
spinnerDebitOrCredit.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
debitOrCredit = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
ibSave.setOnClickListener {
// save all the info.
val date = tvDateAT.text
val memo = etMemo.text
val title = etTitle.text
val amount = etAmount.text
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val serverTS = FieldValue.serverTimestamp()
val collection = account
var nb = 0.0
val newBalance = if (debitOrCredit.contains("Debit")){
nb = (balance.toDouble() - etAmount.text.toString().toDouble())
} else {
nb = (balance.toDouble() + etAmount.text.toString().toDouble())
}
val info = hashMapOf(
"date" to date.toString(),
"type" to type,
"title" to title.toString(),
"memo" to memo.toString(),
"category" to category,
"debitOrCredit" to debitOrCredit,
"amount" to amount.toString(),
"clearReconcile" to "NA",
"mils" to mils,
"timeStamp" to serverTS
)
if (date != "Date"){
if (title?.isNotEmpty() == true && amount?.isNotEmpty() == true){
val dbAccountTransaction = db.collection("Users").document(uid).collection(collection)
dbAccountTransaction.add(info)
.addOnSuccessListener {
db.collection("Users").document(uid).collection(collection).document("balance")
.update("balance", nb.toString())
.addOnSuccessListener {
Toast.makeText(this, "Transaction was saved.", Toast.LENGTH_SHORT).show()
val i = Intent(this, MainActivity::class.java)
startActivity(i)
}
}
.addOnFailureListener{
Toast.makeText(this, "There was an error. Transaction wasn't saved.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please fill out Title and Amount.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please select a date.", Toast.LENGTH_SHORT).show()
}
}
ibCancel.setOnClickListener {
stopSelf()
windowManager.removeView(floatView)
}
LAYOUT_TYPE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_TOAST
}
floatWindowLayoutParams = WindowManager.LayoutParams(
(width * 0.55f).toInt(),
(height * 0.55f).toInt(),
LAYOUT_TYPE!!,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
)
floatWindowLayoutParams.gravity = Gravity.CENTER
floatWindowLayoutParams.x = 0
floatWindowLayoutParams.y = 0
windowManager.addView(floatView, floatWindowLayoutParams)
floatView.setOnTouchListener(object : View.OnTouchListener{
val updatedFloatWindowLayoutParam = floatWindowLayoutParams
private var initialX = 0.0
private var initialY = 0.0
private var initialTouchX = 0.0
private var initialTouchY = 0.0
override fun onTouch(v: View?, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = updatedFloatWindowLayoutParam.x.toDouble()
initialY = updatedFloatWindowLayoutParam.y.toDouble()
initialTouchX = event.rawX.toDouble()
initialTouchY = event.rawY.toDouble()
return true
}
MotionEvent.ACTION_MOVE -> {
updatedFloatWindowLayoutParam.x = (initialX + event.rawX - initialTouchX).toInt()
updatedFloatWindowLayoutParam.y = (initialY + event.rawY - initialTouchY).toInt()
windowManager.updateViewLayout(floatView, updatedFloatWindowLayoutParam)
return true
}
}
return false
}
})
spinnerAccount.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
}
}
return START_NOT_STICKY
}
private fun getDB() {
try {
db.collection("Users").document(FirebaseAuth.getInstance().currentUser!!.uid)
.collection(account).document("balance")
.get()
.addOnSuccessListener { document ->
if (document != null) {
val balanceResult = StringBuffer()
balanceResult.append(document.data?.getValue("balance"))
balance = balanceResult.toString()
}
}
} catch (e: Exception){
e.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
windowManager.removeView(floatView)
}
companion object {
var FOREGROUND_SERVICE_ID = 101
var START = "start"
var STOP_ACTION = "stop"
private const val CHANNEL = "default"
}
}
I have tried several ways of getting the context, but it always seems to come back null. I have tried this, this#Dooa, this.applicationContext, I also created a class MyApp to get context that way. it didn't work either. I used this link. Code below.
class MyApp: Application() {
private var context: Context? = null
override fun onCreate() {
super.onCreate()
context = applicationContext
}
fun getContext(): Context? {
return context?.applicationContext
}
}
I've also checked out this answer about Service is a Context, but I still haven't been able to get this to work.
I have tried the code in the onCreate first then I tried the onStartCommand to no avail. What am I missing?
The window pops up, It pops up with a button click, or from a notification, either way if I click on a view, it gives me the error at the top of this question.
You are doing this:
context = MyApp().getContext()
This is definitely wrong. MyApp is an Android component. You are not allowed to instantiate Android components yourself. Only the Android framework can do this (as it also sets up the Context and other important values.
If you want the Service context, just use this (A Service is a Context).
If you want the application context, just use getApplication() (Application is also a Context).
I want to use Coil image library to load images from the api with the same cookie that was set before. Therefore I want to use the same HttpClient both for my Ktor networking calls and for Image Loading with Coil.
How can I share the same HttpClient between Ktor and Coil? I assume, I need to adjust dependencies somehow, but I can't wrap my head around it.
My KtorApiImpl in shared module
class KtorApiImpl(log: Kermit) : KtorApi {
val baseUrl = BuildKonfig.baseUrl
// If this is a constructor property, then it gets captured
// inside HttpClient config and freezes this whole class.
#Suppress("CanBePrimaryConstructorProperty")
private val log = log
override val client = HttpClientProvider().getHttpClient().config {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
log.v("Network") { message }
}
}
level = LogLevel.INFO
}
}
init {
ensureNeverFrozen()
}
override fun HttpRequestBuilder.apiUrl(path: String) {
url {
takeFrom(baseUrl)
encodedPath = path
}
}
override fun HttpRequestBuilder.json() {
contentType(ContentType.Application.Json)
}
}
actual HttpClientProvider in androidMain
var cookieJar: CookieJar = object : CookieJar {
private val cookieStore: HashMap<String, List<Cookie>> = HashMap()
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
cookieStore[url.host] = cookies
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
val cookies = cookieStore[url.host]
return cookies ?: ArrayList()
}
}
actual class HttpClientProvider actual constructor() {
actual fun getHttpClient(): HttpClient {
return HttpClient(OkHttp) {
engine {
preconfigured = getOkHttpClient()
}
}
}
}
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(cookieJar)
.build()
}
ImageLoaderFactory in androidApp - how to use an HttpClient instead of creating new?
class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(context)
.availableMemoryPercentage(0.25) // Use 25% of the application's available memory.
.crossfade(true) // Show a short crossfade when loading images from network or disk.
.componentRegistry {
add(ByteArrayFetcher())
}
.okHttpClient {
// Create a disk cache with "unlimited" size. Don't do this in production.
// To create the an optimized Coil disk cache, use CoilUtils.createDefaultCache(context).
val cacheDirectory = File(context.filesDir, "image_cache").apply { mkdirs() }
val cache = Cache(cacheDirectory, Long.MAX_VALUE)
// Lazily create the OkHttpClient that is used for network operations.
OkHttpClient.Builder()
.cache(cache)
.build()
}
.build()
}
}
Koin dependencies in androidApp
#Suppress("unused")
class MainApp : Application() {
override fun onCreate() {
super.onCreate()
initKoin(
module {
single<Context> { this#MainApp }
single<AppInfo> { AndroidAppInfo }
single { CoilImageLoaderFactory(get<Context>())}
single<SharedPreferences> {
get<Context>().getSharedPreferences("MAIN_SETTINGS", Context.MODE_PRIVATE)
}
single {
{ Log.i("Startup", "Hello from Android/Kotlin!") }
}
}
)
}
}
And then Main Activity
class MainActivity : AppCompatActivity() {
val loaderFactory: CoilImageLoaderFactory by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompositionLocalProvider(LocalImageLoader provides loaderFactory.newImageLoader()) {
MainTheme {
ProvideWindowInsets {
Surface {
MainScreen()
}
}
}
}
}
}
}
I accessed the OkHttpClient from ImageLoader with
class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory, KoinComponent {
val ktorApiImpl: KtorApi by inject()
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(context)
.componentRegistry {
add(ByteArrayFetcher())
}
.okHttpClient {
val config = ktorApiImpl.client.engine.config as OkHttpConfig
config.preconfigured as OkHttpClient
}
.build()
}
You could use ImageLoader.Builder.callFactory{} to provide your own Call.Factory used for network requests. The downside is you would have to map whatever type your KtorApiImpl returns to okttp3.Response which Coil understands.
Here’s a sample that describes how to implement the Call.Factory interface and provide it to Coil’s ImageLoader
ImageLoader.Builder(context)
.callFactory {
Call.Factory {
object: Call {
private var job: Job? = null
override fun clone(): Call {
TODO(“Not yet implemented”)
}
override fun request(): Request {
return it
}
override fun execute(): Response {
return runBlocking {
// Call KTOR client here
}
}
override fun enqueue(responseCallback: Callback) {
// Use a proper coroutines scope
job = GlobalScope.launch {
// Call KTOR client here
}
}
override fun cancel() {
job?.cancel()
}
override fun isExecuted(): Boolean {
return job?.isCompleted ?: false
}
override fun isCanceled(): Boolean {
return job?.isCancelled ?: false
}
override fun timeout(): Timeout {
// Your Timeout here
}
}
}
}
I have following route:
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().map { releases in
var result: [Page] = []
for p in pages {
let page = p
var pageEvents: [Event] = []
for e in p.events {
let event = e
if let release = releases.first(where: { $0.fbId == e.id }) {
event.inProgress = release.inProgress
event.purpose = release.purpose
_ = try release.testPrices.query(on:req).all().map { testP in
event.testPrices = testP // <--- this line is not applied
}
} else {
event.inProgress = false
}
pageEvents.append(event)
}
page.events = pageEvents
result.append(page)
}
return result
}
}
Unfortunatelly event.testPrices = testP is not applied, it will e not part of the response. What can I do? At some cases I do not need to postpone "return". How can I dissolve scheduling issue?
I do a ~~ operation on TestPrice also as for Release before.
router.post([Page].self, at: "/fetchStatusOfManagedReleases") { (req, pages) -> Future<[Page]> in
let eventIds = pages.map { $0.events }.flatMap { $0 }.map { $0.id }
return Release.query(on: req).filter(\.fbId ~~ eventIds).all().flatMap { releases in
let releaseInnerIds = releases.map {$0.id}
return TestPrice.query(on: req).filter(\.id ~~ releaseInnerIds).all().map { testPrices in
var result: [Page] = []
for p in pages {
let page = p
var pageEvents: [Event] = []
for e in p.events {
let event = e
if let release = releases.first(where: { $0.fbId == e.id }) {
event.inProgress = release.inProgress
event.purpose = release.purpose
event.testPrices = testPrices.compactMap({testPrice in
if testPrice.release.parentID == release.id {
return testPrice
} else {
return nil
}
})
} else {
event.inProgress = false
}
pageEvents.append(event)
}
page.events = pageEvents
result.append(page)
}
return result
}
}
}
My situation is When user enter loading fragment, check LoggedIn, true go straight to MainFragment, false jump to LoginFramgnet.
here is LoadingFragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Logger.t(LoadingFragment::class.java.simpleName).i("onCreateView")
val binding = LoadingFragmentBinding.inflate(inflater, container, false)
subscribeUi()
return binding.root
}
fun subscribeUi(){
val factory: LoadingViewModelFactory = InjectorUtils.provideLoadingViewModelFactory()
val viewModel: LoadingViewModel = ViewModelProviders.of(this, factory).get(LoadingViewModel::class.java)
Logger.t(LoadingFragment::class.java.simpleName).i("viewModel = " + viewModel.toString())
viewModel.checkLogin()
viewModel.isToLogin.observe(viewLifecycleOwner, Observer {
if (it){
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
}else{
Logger.t(LoadingFragment::class.java.simpleName).i("to start login")
findNavController().navigate(R.id.start_login)
}
})
}
here is LoadingViewModel:
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
val isToLogin: MediatorLiveData<Boolean> = MediatorLiveData()
fun checkLogin(){
isToLogin.addSource(loadingRepository.checkLogin()) {
isToLogin.value = it
}
}
}
here is the Loadingrepository:
fun checkLogin() : MutableLiveData<Boolean> {
val data: MutableLiveData<Boolean> = MutableLiveData()
api.httpGet(SDBUrl.CHECK_LOGIN).enqueue(object : Callback<Map<String, Any>>{
override fun onFailure(call: Call<Map<String, Any>>, t: Throwable) {
data.value = false
}
override fun onResponse(call: Call<Map<String, Any>>, response: Response<Map<String, Any>>) {
val result = response.body()
if (result != null && result.containsKey("success")){
val isLogin = result["success"] as Boolean
data.value = isLogin
}else{
data.value = false
}
}
})
return data
}
when logged in, popbackto LoadingFragment,isToLogin observe execute else immediately, LoginFragment start agagin. when I debug, wait a while on LoginFragment popBackStack, then goback to Loading Fragment,isToLogin observe execute true.so I am very confused, how can I fix this.
Finally, I solved this problem as follow.
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
fun checkLogin(): MediatorLiveData<Boolean> {
val isToLogin : MediatorLiveData<Boolean> = MediatorLiveData()
isToLogin.addSource(loadingRepository.checkLogin()) {
Logger.t(LoadingViewModel::class.java.simpleName).i("it = $it")
isToLogin.value = it
}
return isToLogin
}
}
then in LoadingFragment,:
loadingViewModel.checkLogin().observe(viewLifecycleOwner, Observer {
Logger.i("isToLogin = $it")
if (it) {
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
} else {
findNavController().navigate(R.id.start_login)
}
})