You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Android Activity创建时数据不显示,需二次加载问题求助

Hey Andrea, let's break down why this is happening and how to fix it properly:

The Root Problem

Your Volley network requests are asynchronous—they run in the background while your Activity and Fragments finish initializing. When the requests complete and populate those Companion Object global variables, your Fragments have already finished inflating their UI and won't automatically refresh. Restarting the Activity works because the Fragments re-read those global variables during their next initialization.

Plus, using Companion Object for shared data is risky: it's not lifecycle-aware, can cause memory leaks, and leads to inconsistent state across configuration changes (like rotating the screen).

The Fix: Use ViewModel + LiveData

We'll refactor your code to follow the MVVM pattern, which ensures data updates automatically trigger UI refreshes without needing to restart the Activity.

Step 1: Create a Shared ViewModel

This ViewModel will handle all network requests and store data using LiveData, which notifies Fragments when data changes.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import org.json.JSONObject

class SharedViewModel : ViewModel() {
    // Traffic data (mutable for internal updates, immutable for observers)
    private val _trafficData = MutableLiveData<TrafficData>()
    val trafficData: LiveData<TrafficData> = _trafficData

    // IP Blocked data
    private val _ipBlockedData = MutableLiveData<IpBlockedData>()
    val ipBlockedData: LiveData<IpBlockedData> = _ipBlockedData

    // Search Terms data
    private val _searchTermsData = MutableLiveData<SearchTermsData>()
    val searchTermsData: LiveData<SearchTermsData> = _searchTermsData

    // Fetch Traffic Dashboard data
    fun fetchTrafficData(context: android.content.Context, baseUrl: String, token: String, filter: String?) {
        val queue = Volley.newRequestQueue(context)
        val requestUrl = "$baseUrl/homeApp?token=$token&filter=${filter ?: ""}"

        val stringRequest = StringRequest(
            Request.Method.GET, requestUrl,
            Response.Listener { response ->
                val jsonObj = JSONObject(response)
                val status = jsonObj.getInt("status")
                val clicksArray = jsonObj.getJSONArray("clicks")
                val blocked = jsonObj.getInt("bloccati")
                val totalAdwordsClicks = jsonObj.getInt("totalClicksAdwords")

                val clicksList = mutableListOf<TrafficClick>()
                for (i in 0 until clicksArray.length()) {
                    val item = clicksArray.getJSONObject(i)
                    val click = TrafficClick(
                        item.getString("id"),
                        item.getString("ip"),
                        item.getString("adwords"),
                        item.getString("browser"),
                        item.getString("keyword"),
                        item.getString("id_campaign"),
                        item.getString("os"),
                        item.getString("mobile"),
                        item.getString("guid"),
                        item.getString("referer"),
                        item.getString("from_referer"),
                        item.getString("organization"),
                        item.getString("data_creation"),
                        item.getString("data_update"),
                        item.getString("eliminato"),
                        item.getString("counter"),
                        item.getString("id_dominio"),
                        item.getString("id_geo_ip"),
                        item.getString("iso_code")
                    )
                    if (!click.eliminato.isNullOrEmpty()) clicksList.add(click)
                }

                // Update LiveData (triggers Fragment UI refresh)
                _trafficData.postValue(
                    TrafficData(
                        status.toString(),
                        clicksList.size.toString(),
                        totalAdwordsClicks.toString(),
                        blocked.toString(),
                        clicksList
                    )
                )
            },
            Response.ErrorListener {
                _trafficData.postValue(null) // Signal error to Fragment
            }
        )
        queue.add(stringRequest)
    }

    // Fetch IP Blocked data (similar pattern to Traffic)
    fun fetchIpBlockedData(context: android.content.Context, baseUrl: String, token: String) {
        val queue = Volley.newRequestQueue(context)
        val requestUrl = "$baseUrl/listIpBlocked?token=$token"

        val stringRequest = StringRequest(
            Request.Method.GET, requestUrl,
            Response.Listener { response ->
                val jsonObj = JSONObject(response)
                val status = jsonObj.getInt("status")
                val ipsArray = jsonObj.getJSONArray("ips")

                val ipsList = mutableListOf<IpListBlock>()
                for (i in 0 until ipsArray.length()) {
                    val item = ipsArray.getJSONObject(i)
                    val ip = IpListBlock(
                        item.getInt("id"),
                        item.getInt("id_user"),
                        item.getInt("id_dominio"),
                        item.getInt("id_criterion"),
                        item.getInt("id_adwords_campaign"),
                        item.getInt("id_adwords_customer"),
                        item.getString("ip"),
                        item.getString("trigger"),
                        item.getString("created_at"),
                        item.getString("removal_date"),
                        item.getString("unlimited"),
                        item.getString("updated_at"),
                        item.getString("deleted_at"),
                        item.getInt("id_click"),
                        item.getString("organization"),
                        item.getString("iso_code")
                    )
                    ipsList.add(ip)
                }

                _ipBlockedData.postValue(
                    IpBlockedData(
                        status.toString(),
                        ipsList.size,
                        ipsList
                    )
                )
            },
            Response.ErrorListener {
                _ipBlockedData.postValue(null)
            }
        )
        queue.add(stringRequest)
    }

    // Fetch Search Terms data (similar pattern)
    fun fetchSearchTermsData(context: android.content.Context, baseUrl: String, token: String, filter: String?) {
        val queue = Volley.newRequestQueue(context)
        val requestUrl = "$baseUrl/reportApp?token=$token&filter=${filter ?: ""}"

        val stringRequest = StringRequest(
            Request.Method.GET, requestUrl,
            Response.Listener { response ->
                val jsonObj = JSONObject(response)
                val status = jsonObj.getInt("status")
                val searchTermsArray = jsonObj.getJSONArray("searchTerms")
                val clicks = jsonObj.getInt("clicks")
                val blocked = jsonObj.getInt("bloccati")
                totalClickAdwordsSearch = jsonObj.getInt("bloccati").toString()

                val searchTermsList = mutableListOf<SearchTermsList>()
                for (i in 0 until searchTermsArray.length()) {
                    val item = searchTermsArray.getJSONObject(i)
                    val searchTerm = SearchTermsList(
                        item.getInt("id"),
                        item.getInt("id_user"),
                        item.getInt("id_adwords_customer"),
                        item.getInt("id_adwords_campaign"),
                        item.getString("campaign"),
                        item.getString("search_term"),
                        item.getString("keyword"),
                        item.getInt("keyword_id"),
                        item.getInt("clicks"),
                        item.getInt("impressions"),
                        item.getInt("interactions"),
                        item.getString("interactions_type"),
                        item.getInt("conversions"),
                        item.getInt("cost_conv"),
                        item.getInt("total_conv"),
                        item.getInt("cost"),
                        item.getInt("avg_cpv"),
                        item.getString("currency"),
                        item.getString("device"),
                        item.getString("date"),
                        item.getString("created_at"),
                        item.getString("updated_at"),
                        item.getString("deleted_at")
                    )
                    searchTermsList.add(searchTerm)
                }

                _searchTermsData.postValue(
                    SearchTermsData(
                        status.toString(),
                        clicks.toString(),
                        blocked.toString(),
                        totalClickAdwordsSearch,
                        searchTermsList.size,
                        searchTermsList
                    )
                )
            },
            Response.ErrorListener {
                _searchTermsData.postValue(null)
            }
        )
        queue.add(stringRequest)
    }

    // Data classes to encapsulate each screen's data
    data class TrafficData(
        val status: String,
        val totalClicks: String,
        val totalAdwordsClicks: String,
        val totalBlocked: String,
        val clicks: List<TrafficClick>
    )

    data class IpBlockedData(
        val status: String,
        val ipsNumber: Int,
        val ips: List<IpListBlock>
    )

    data class SearchTermsData(
        val status: String,
        val totalClicks: String,
        val totalBlocked: String,
        val totalAdwordsClicks: String,
        val searchTermsLength: Int,
        val searchTerms: List<SearchTermsList>
    )
}

Step 2: Update MainActivity to Use the ViewModel

Remove the Companion Object global variables and initialize the ViewModel to handle requests:

package com.example.example
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize ViewModel
        sharedViewModel = ViewModelProvider(this)[SharedViewModel::class.java]

        // Get saved preferences
        val sharedPreferences = getSharedPreferences("TOKEN_FILE", Context.MODE_PRIVATE)
        val baseUrl = sharedPreferences.getString("URL", "") ?: ""
        val token = sharedPreferences.getString("TOKEN", "") ?: ""
        val trafficFilter = sharedPreferences.getString("changeDataTraffic", "")
        val searchTermsFilter = sharedPreferences.getString("changeDataSearchTerms", "")

        // Launch all data requests
        sharedViewModel.fetchTrafficData(this, baseUrl, token, trafficFilter)
        sharedViewModel.fetchIpBlockedData(this, baseUrl, token)
        sharedViewModel.fetchSearchTermsData(this, baseUrl, token, searchTermsFilter)

        // Setup bottom navigation
        val navView: BottomNavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)
        navView.setupWithNavController(navController)
    }
}

Step 3: Update Fragments to Observe LiveData

Modify your TrafficFragment (and other Fragments) to watch for data changes and update UI automatically:

package com.example.example.ui.traffic
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.example.SharedViewModel
import com.example.example.databinding.FragmentTrafficBinding

class TrafficFragment : Fragment() {
    private var _binding: FragmentTrafficBinding? = null
    private val binding get() = _binding!!
    private lateinit var sharedViewModel: SharedViewModel
    private lateinit var trafficAdapter: TrafficAdapter // Replace with your actual adapter

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentTrafficBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        sharedViewModel = ViewModelProvider(requireActivity())[SharedViewModel::class.java]

        // Initialize RecyclerView
        trafficAdapter = TrafficAdapter()
        binding.recyclerViewTraffic.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = trafficAdapter
        }

        // Observe Traffic data updates
        sharedViewModel.trafficData.observe(viewLifecycleOwner) { trafficData ->
            trafficData?.let {
                // Update UI elements
                binding.totalClicksText.text = it.totalClicks
                binding.totalBlockedText.text = it.totalBlocked
                binding.totalAdwordsText.text = it.totalAdwordsClicks
                trafficAdapter.submitList(it.clicks) // Update recycler view data
            } ?: run {
                // Handle error state (e.g., show error message)
                binding.errorMessage.visibility = View.VISIBLE
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null // Prevent memory leaks
    }
}

Why This Works

  1. Lifecycle Awareness: The ViewModel persists across configuration changes (like screen rotation) and doesn't leak memory.
  2. Automatic UI Updates: LiveData notifies your Fragment whenever data changes, so you don't need to restart the Activity to see new data.
  3. Cleaner Architecture: Separates data fetching from UI logic, making your code easier to maintain and test.

Bonus: Refresh Data After Modifications

When you update filters or modify data, call the corresponding fetch method in the ViewModel again. For example:

// In your Fragment when a filter is changed
sharedViewModel.fetchTrafficData(requireContext(), baseUrl, token, newFilterValue)

The LiveData will update, and your Fragment will refresh automatically.

内容的提问来源于stack exchange,提问作者Andrea Giannoni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 10:32:41