Skip to main content
Follow these proven patterns for production-ready implementation of the Stringboot Android SDK.

1. Use autoInitialize() Pattern

Initialize the SDK in your Application class using the autoInitialize() method. This is the recommended approach used in the official demo app.
  • Good
  • Bad
class StringbootApplication : Application() {
    private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    override fun onCreate() {
        super.onCreate()
        val success = StringbootExtensions.autoInitialize(this)

        if (success) {
            // Synchronously preload to prevent flash
            runBlocking {
                StringProvider.preloadLanguage(StringProvider.deviceLocale(), maxStrings = 500)
            }

            // Background sync
            applicationScope.launch {
                StringProvider.refreshFromNetwork(StringProvider.deviceLocale())
            }
        }
    }
}
Why This Pattern Works:
  • Initializes before Activity creation
  • Prevents UI flashing with synchronous preload
  • Fetches fresh data in background
  • Works with offline mode fallback

2. Use applyStringbootTags() for XML Integration

Leverage XML tags for zero-code string integration instead of manual TextView updates.
  • Good
  • Bad
<!-- activity_main.xml -->
<TextView
    android:tag="welcome_message"
    android:text="@string/welcome_message" />
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    binding.root.applyStringbootTags()  // Auto-applies to all tagged TextViews
}
Advantages:
  • Declarative and maintainable
  • Single call updates all tagged views
  • Easier to refactor and audit
  • Better performance than individual calls

3. Preload on Language Change

Always preload the cache when switching languages to avoid UI flickering.
  • Good
  • Bad
lifecycleScope.launch {
    StringProvider.setLocale("es")
    StringProvider.preloadLanguage("es", 500)  // Warm cache
    // Subsequent accesses are instant
    binding.root.applyStringbootTags()
}
Benefits:
  • Prevents “flash of stale content”
  • Instant string access after preload
  • Better user experience
  • Only loads most common strings

4. Use Flow for Reactive UI

Use Kotlin Flow to automatically update UI when language changes or sync completes.
  • Good
  • Bad
lifecycleScope.launch {
    StringProvider.getFlow("key", "en").collect { text ->
        binding.textView.text = text  // Auto-updates on language change
    }
}
Flow Advantages:
  • Reactive to language changes
  • Updates on network sync completion
  • Lifecycle-aware collection
  • Perfect for dynamic content

5. Handle Missing Strings Gracefully

Always check for missing strings and provide fallback text.
  • Good
  • Bad
val text = StringProvider.get("key", "en")
if (text.startsWith("??")) {
    binding.textView.text = "Default Text"  // Fallback
} else {
    binding.textView.text = text
}
Fallback Strategies:
  • Check for ??key?? pattern
  • Provide sensible default text
  • Log warnings for debugging
  • Improve user experience

6. Use withContext for FAQ Calls

Always fetch FAQs on a background thread to prevent blocking the main thread.
  • Good
  • Bad
lifecycleScope.launch {
    val faqs = withContext(Dispatchers.IO) {
        FAQProvider.getFAQs(tag = "payments", lang = "en")
    }
    // Update UI with faqs
    adapter.updateFAQs(faqs)
}
Thread Safety:
  • IO operations off main thread
  • Prevents ANR (Application Not Responding)
  • Better responsiveness
  • Proper coroutine context handling

7. Save Language Preferences

Persist user language preferences to restore on app restart.
  • Good
  • Bad
private fun switchLanguage(newLanguage: ActiveLanguage) {
    lifecycleScope.launch {
        currentLanguage = newLanguage.code
        StringProvider.setLocale(currentLanguage)

        // Save preference
        prefs.edit { putString("current_language", currentLanguage) }

        // ... rest of language switching
    }
}
Then restore in onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Restore saved language
    currentLanguage = prefs.getString("current_language", "en") ?: "en"
    StringProvider.setLocale(currentLanguage)
}
Persistence Benefits:
  • Respects user preferences
  • Consistent experience across sessions
  • Better user satisfaction
  • Minimal storage overhead

8. Use Appropriate Cache Sizes

Configure cache size based on your app’s string count to balance memory and performance.
  • Good
  • Bad
// For apps with 100-500 unique strings
val cacheSize = 500

// For apps with 1000+ strings
val cacheSize = 1000

// For large apps with 5000+ strings
val cacheSize = 2000
Cache Size Guidelines:
String CountRecommended Cache
100-500500
500-10001000
1000-50001500-2000
5000+2000-5000
Cache Strategy:
  • Too small: Frequent DB queries (slow)
  • Too large: Excessive memory use
  • Right-sized: Balance performance and memory
  • Monitor with getCacheStats()


9. A/B Testing Best Practices

Use consistent device IDs and proper analytics integration for reliable A/B testing.
  • Good
  • Bad
class StringbootApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Define analytics handler before initialization
        val analyticsHandler = object : StringbootAnalyticsHandler {
            override fun onExperimentsAssigned(experiments: Map<String, ExperimentAssignment>) {
                experiments.forEach { (key, assignment) ->
                    // Set user properties (not events) for experiment tracking
                    firebaseAnalytics.setUserProperty(
                        "stringboot_exp_$key",
                        assignment.variantName
                    )
                }
            }
        }

        // Use consistent device ID across all SDKs
        StringbootExtensions.autoInitialize(
            context = this,
            providedDeviceId = getAppDeviceId(), // Consistent across app
            analyticsHandler = analyticsHandler
        )
    }

    private fun getAppDeviceId(): String? {
        // Use Firebase Installation ID or similar persistent ID
        // return FirebaseInstallations.getInstance().id.await()
        return null // Let SDK generate UUID if no custom ID
    }
}
A/B Testing Guidelines:
  • Use persistent, installation-level device IDs (not session IDs)
  • Set experiments as user properties in analytics (not events)
  • Use the same device ID across all SDKs in your app
  • Let SDK handle experiment assignment automatically
  • Don’t manually assign users to variants
  • Test experiment tracking in development before release

Summary

Following these 9 best practices ensures:
  1. Fast app startup without UI flashing
  2. Responsive language switching
  3. Graceful offline functionality
  4. Optimal memory usage
  5. Better user experience
  6. Maintainable code structure
  7. Production-ready implementation
  8. Easier debugging and troubleshooting
  9. Reliable A/B testing with proper analytics tracking
Implement these patterns in your production apps for best results with the Stringboot Android SDK.