【问题标题】:How to disconnect from Firebase emulator using Android Hilt如何使用 Android Hilt 断开与 Firebase 模拟器的连接
【发布时间】:2022-08-14 02:08:35
【问题描述】:

我正在尝试使用 Firebase Auth 和 Firestore 模拟器进行测试,但我真正的 Firebase 应用程序用于开发。我有用于依赖注入的 Hilt。在我的测试模块中,我设置了useEmulator,但在我的开发模块中,我只使用了 Firebase 单例。事实证明,开发仍在使用模拟器,因为单例在测试和开发之间共享。如何在开发模块中断开与模拟器的连接?

开发模块:

@Module
@InstallIn(SingletonComponent::class)
object FirebaseModule {
    @Singleton
    @Provides
    fun provideAuth(): FirebaseAuth = Firebase.auth

    @Singleton
    @Provides
    fun provideDb(): FirebaseFirestore = Firebase.firestore
}

测试模块:

@Module
@TestInstallIn(components = [SingletonComponent::class], replaces = [FirebaseModule::class])
object FakeFirebaseModule {
    private val TAG = FakeFirebaseModule::class.simpleName

    @Singleton
    @Provides
    fun provideAuth(): FirebaseAuth = Firebase.auth.apply {
        try {
            useEmulator(\"10.0.2.2\", 9099)
        } catch (e: IllegalStateException) {
            Log.e(TAG, \"User emulator failed\", e)
        }
    }

    @Singleton
    @Provides
    fun provideDb(): FirebaseFirestore = Firebase.firestore.apply {
        try {
            useEmulator(\"10.0.2.2\", 8080)
        } catch (e: IllegalStateException) {
            Log.e(TAG, \"User emulator failed\", e)
        }
        firestoreSettings = FirebaseFirestoreSettings.Builder().setPersistenceEnabled(false).build()
    }
}

测试:

@UninstallModules(FirebaseModule::class)
@HiltAndroidTest
@MediumTest
class ExampleTest {
    private val hiltRule = HiltAndroidRule(this)

    private val composeTestRule = createAndroidComposeRule<MainActivity>()

    @get:Rule
    val rule: TestRule = RuleChain.outerRule(hiltRule).around(composeTestRule)

    @Inject
    lateinit var auth: FirebaseAuth

    @Inject
    lateinit var db: FirebaseFirestore

    @Before
    fun setUp() {
        hiltRule.inject()
        auth.createUserWithEmailAndPassword(TestData.UserEmail1, TestData.UserPassword1)
            .addOnFailureListener {
                auth.signInWithEmailAndPassword(TestData.UserEmail1, TestData.UserPassword1)
            }
    }

    // ...
}

    标签: android firebase kotlin dagger-hilt firebase-tools


    【解决方案1】:

    模拟器(Firestore)似乎没有断开连接功能,并且连接时字段是私有的。见this link

    在这种情况下,删除 @Singleton 以获取不同的 Firestore 实例,或将单例范围限定到不同的(测试与生产)环境将使生产 Firestore 无法连接到模拟器。

    【讨论】:

    • 我删除了@Singleton 并将SingletonComponent 更改为ViewModelComponent。我仍然遇到同样的问题。
    • 没关系。我弄清楚出了什么问题,而且它超出了左场
    【解决方案2】:

    *叹气*我发现出了什么问题,我完全看错了地方。我以为我的开发代码使用的是测试数据,但实际上它使用的是陈旧的开发数据。我的代码中有这个:

    inline fun <reified T : Any> Query.getObjectsFlow() = callbackFlow {
        val subscription = addSnapshotListener { value, error ->
            when {
                error != null -> cancel("Get snapshot failed", error)
                value != null -> trySend(value.toObjects<T>())
            }
        }
    
        awaitClose { subscription.remove() }
    }
    

    像这样使用:

    // Repository
    
    val feed = db.collection(FEED_COLLECTION)
                .getObjectsFlow<FeedItemDocument>()
                .map { docs ->
                    val feedItems = docs.map { FeedItemState.fromDocument(it) }
                    if (feedItems.isEmpty()) {
                        LoadState.Empty()
                    } else {
                        LoadState.Data(Feed(feedItems))
                    }
                }
                .catch {
                    Log.e(TAG, "Error querying server", it)
                    emit(LoadState.Failure(it))
                }
    
    // FeedItemState
    
    data class FeedItemState(
        val id: String = "",
        val createdAt: Instant = Instant.now(),
        val text: String = "",
    ) {
        companion object {
            fun fromDocument(doc: FeedItemDocument): FeedItemState {
                val id = doc.id!!
                val createdAt = doc.createdAt?.toDate()?.toInstant()!!
                val text = doc.text!!
                return FeedItemState(type, id, createdAt, text)
            }
        }
    }
    

    如果任何文档字段为空,我的存储库将发出加载失败值。但是,快照侦听器会发出每个查询对象,包括旧查询对象。这意味着当我将字段添加到数据库时,发出的第一个对象将没有新字段,从而引发 NPE。

    我不得不更改存储库以接受和忽略空字段。因此,将docs.map { FeedItemState.fromDocument(it) } 更改为mapNotNull 并允许fromDocument 方法在字段为空时返回空,而不是使用!!

    经验教训:在 Kotlin 中,如果您使用 !!,请对代码非常怀疑。它通常可以用不会抛出 NPE 的东西代替。

    【讨论】:

      猜你喜欢
      • 2014-11-09
      • 2021-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 2011-08-26
      • 1970-01-01
      相关资源
      最近更新 更多