|
**Filename 2:** `FirebaseStructureFetcher.kt` |
|
|
|
class FirebaseStructureFetcher( |
|
private val fileHelper: FileHelper, |
|
private val firebaseService: FirebaseService, |
|
private val jsonBuilder: JsonBuilder, |
|
// SupervisorJob evita que un error de Firebase mate el Scope |
|
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) |
|
) { |
|
fun fetch() { |
|
Log.d("TEST_LOGGER", "Iniciando descarga...") |
|
scope.launch { // Empezamos en el scope por defecto |
|
try { |
|
firebaseService.checkRealServerStatus().let { status -> |
|
Log.d("TEST_LOGGER", "Estado del servidor: $status") |
|
} |
|
|
|
Log.d("TEST_LOGGER", "Consultando Firebase...") |
|
// 1. Llamada asíncrona a Firebase (hilo secundario por await) |
|
val snapshot = firebaseService.fetchDatabaseStructure() |
|
|
|
Log.d("TEST_LOGGER", "Construyendo JSON...") |
|
// 2. Procesamiento pesado en hilo Default (CPU) |
|
val json = withContext(Dispatchers.Default) { |
|
jsonBuilder.buildJson(snapshot) |
|
} |
|
|
|
// 3. Guardado en disco en hilo IO |
|
withContext(Dispatchers.IO) { |
|
fileHelper.saveJsonToFile(json, "structure.json") |
|
.onSuccess { Log.d("TEST_LOGGER", "OK: Guardado en ${it.absolutePath}") } |
|
.onFailure { Log.e("TEST_LOGGER", "ERROR: No se pudo guardar", it) } |
|
} |
|
|
|
} catch (e: Exception) { |
|
Log.e("TEST_LOGGER", "Fallo en Firebase/Proceso", e) |
|
} |
|
} |
|
} |
|
} |
|
|
|
**Filename 3:** `FirebaseService.kt` |
|
|
|
class FirebaseService { |
|
suspend fun fetchDatabaseStructure(): DataSnapshot { |
|
val rootRef = FirebaseDatabase.getInstance().reference |
|
// .get().await() es la forma moderna (2026) de obtener datos sin callbacks |
|
return rootRef.get().await() |
|
} |
|
|
|
suspend fun checkRealServerStatus(): String { |
|
return try { |
|
// En 2026, withTimeout es vital para no dejar la app colgada |
|
withTimeout(5000) { |
|
val db = FirebaseDatabase.getInstance() |
|
// Intentamos obtener el Snapshot de la raíz |
|
db.reference.get().await() |
|
"✅ ONLINE_AND_ACTIVE" |
|
} |
|
} catch (e: Exception) { |
|
// Si pasan los 5 segundos sin respuesta de Google: |
|
if (e is kotlinx.coroutines.TimeoutCancellationException) { |
|
"❌ TIMEOUT: El proyecto existe en el JSON pero el servidor NO responde (Proyecto borrado)." |
|
} else { |
|
"❌ ERROR: ${e.message}" |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
**Filename 4:** `JsonBuilder.kt` |
|
|
|
class JsonBuilder { |
|
@Throws(JSONException::class) |
|
fun buildJson(dataSnapshot: DataSnapshot): JSONObject { |
|
val json = JSONObject() |
|
for (child in dataSnapshot.children) { |
|
if (child.hasChildren()) { |
|
json.put(child.key, buildJson(child)) |
|
} else { |
|
json.put(child.key, child.value) |
|
} |
|
} |
|
return json |
|
} |
|
} |
|
|
|
**Filename 5:** `FileHelper.kt` |
|
|
|
class FileHelper(private val context: Context) { |
|
fun saveJsonToFile(json: JSONObject, fileName: String): Result<File> { |
|
return runCatching { |
|
val file = File(context.getExternalFilesDir(null), fileName) |
|
file.writeText(json.toString(4)) |
|
file // Si esto se ejecuta, runCatching devuelve Success(file) |
|
} |
|
} |
|
} |
|
|
|
--- |
if works if result is
ONLINE_AND_ACTIVE
se ejecuta en un projecto Android Kotlin en Android studio
no importa si es google-services.json es de otro projecto
solo se cambia el