Resources not getting updated after changing locale - android

My app supports two languages(English and Spanish). When I change the locale from English(en) to Spanish(es) it succeeds (Locale get changed). But, resources are not getting updated. I am recreating the activity after updated the Locale.
I have debugged the app on Splash screen after changing the Locale. and check the current Locale (returned 'es'). but still, the strings are not getting updated. The Spanish string file is put under the values-es package.
Also tried running the same localization code by creating a new application and it worked.
Following is the code:
VerifiApp.kt:
class VerifiApp : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
AppInjector.init(this)
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleHelper.onAttach(base!!, "en"))
}
override fun activityInjector() = dispatchingAndroidInjector
}
SelectLanguageActivity.kt:
class SelectLanguageActivity : BaseActivity<SelectLanguageViewModel>() {
private var languageCode = ""
#Inject
lateinit var appDataManager: AppDataManager
#Inject
lateinit var appViewModelFactory: AppViewModelFactory
private lateinit var selectLanguageViewModel: SelectLanguageViewModel
private lateinit var languageAdapter: ArrayAdapter<Language>
private var languageList = ArrayList<Language>()
override fun getViewModel(): SelectLanguageViewModel {
selectLanguageViewModel = ViewModelProviders.of(this#SelectLanguageActivity, appViewModelFactory)
.get(SelectLanguageViewModel::class.java)
return selectLanguageViewModel
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_select_language)
languageList.add(Language("English", "en"))
languageList.add(Language("Spanish", "es"))
languageAdapter = ArrayAdapter(this, R.layout.item_spinner_dropdown, languageList)
spinnerLanguage.adapter = languageAdapter
spinnerLanguage.apply {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
languageCode = languageList[position].languageCode
}
}
}
btnUpdate.setOnClickListener {
val context = LocaleHelper.setLocale(this, languageCode)
val resources = context.resources
val bundle = Bundle()
bundle.putString("language_code", languageCode)
val intent = Intent(applicationContext, AuthActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}
}
BaseActivity.kt:
abstract class BaseActivity<out V : ViewModel> : AppCompatActivity(), BaseFragment.Callback {
private lateinit var mViewModel: V
private lateinit var progressDialog: Dialog
override fun onCreate(savedInstanceState: Bundle?) {
performDependencyInjection()
super.onCreate(savedInstanceState)
initializeProgressLoader()
}
private fun initializeProgressLoader() {
progressDialog = Dialog(this)
progressDialog.setCancelable(false)
progressDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
progressDialog.window.setBackgroundDrawableResource(android.R.color.transparent)
progressDialog.setContentView(R.layout.dialog_progress)
}
fun setProgressVisibility(visible: Boolean) {
if (visible) {
progressDialog.show()
} else {
progressDialog.dismiss()
}
}
private fun performDependencyInjection() {
AndroidInjection.inject(this)
mViewModel = getViewModel()
}
override fun onFragmentAttached() {
}
override fun onFragmentDetached(tag: String) {
}
fun isNetworkConnected(): Boolean {
val flag = NetworkUtils.isNetworkConnected(applicationContext)
if (!flag) {
showErrorToast("Internet not connected!")
}
return flag
}
fun hideKeyboard() {
val view: View? = this.currentFocus
val inputMethodManager: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0)
}
fun showErrorToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.ERROR, false).show()
}
fun showInfoToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.INFO, false).show()
}
fun showSuccessToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.SUCCESS, false).show()
}
/**
* Override for set view model
*
* #return ViewModel instance
* */
abstract fun getViewModel(): V
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(LocaleHelper.onAttach(newBase!!))
}
}
LocaleHelper.java
public class LocaleHelper {
private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";
public static Context onAttach(Context context) {
String lang = getPersistedData(context, Locale.getDefault().getLanguage());
return setLocale(context, lang);
}
public static Context onAttach(Context context, String defaultLanguage) {
String lang = getPersistedData(context, defaultLanguage);
return setLocale(context, lang);
}
public static String getLanguage(Context context) {
return getPersistedData(context, Locale.getDefault().getLanguage());
}
public static Context setLocale(Context context, String language) {
persist(context, language);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, language);
}
return updateResourcesLegacy(context, language);
}
private static String getPersistedData(Context context, String defaultLanguage) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(SELECTED_LANGUAGE, defaultLanguage);
}
private static void persist(Context context, String language) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString(SELECTED_LANGUAGE, language);
editor.apply();
}
#TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
#SuppressWarnings("deprecation")
private static Context updateResourcesLegacy(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale);
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
}
Help would be really appreciated. Thanks.

Related

Launch new Activity from FrameLayout

I try to study overlay library for kotlin applications https://github.com/KoderLabs/overlay-service. Right now I have a problem related with opening a new activity from a button which is located inside a FrameLayout class.
So, the task is push on button on Main Activity -> open new overlay small window and roll up Main Activity -> push on button inside a small window -> open new Activity.
In this code nothing happens after the clicking on button.
The project include main 3 classes:
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_simple_pip.setOnClickListener {
checkDrawOverlayPermission(IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
finishAffinity()
}
}
private fun checkDrawOverlayPermission(code: Int) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
} else {
openFloatingWindow(code)
}
} else {
openFloatingWindow(code)
}
}
private fun openFloatingWindow(code: Int) {
when (code) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE -> {
val intent = Intent(this, ImplementPipOverlayService::class.java)
val videoUrl =
"https://s3.amazonaws.com/data.development.momentpin.com/2019/7/3/1562152168485485-0661a550-9d83-11e9-9028-d7af09cf782e.mp4"
val notificationTitle = "Pip Overlay"
val notificationDescription = "Pip overlay description"
val notificationIcon = R.drawable.ic_launcher_foreground
val closeBtnColor = android.R.color.black
val closeBtnBgColor = android.R.color.transparent
intent.putExtra(ImplementPipOverlayService.KEY_STRING_VIDEO_URL, videoUrl)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_DESCRIPTION, notificationDescription)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_TITLE, notificationTitle)
intent.putExtra(ImplementPipOverlayService.KEY_INT_NOTIFICATION_ICON, notificationIcon)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_COLOR, closeBtnColor)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_BG_COLOR, closeBtnBgColor)
ContextCompat.startForegroundService(this, intent)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE,
PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE -> {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
openFloatingWindow(requestCode)
}
} else {
openFloatingWindow(requestCode)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
const val IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE = 251
const val PIP_OVERLAY_REQUEST_CODE = 252
const val RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE = 253
const val RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE = 254
}
ImplementPipOverlayService
class ImplementPipOverlayService : PipOverlayService() {
var videoUrl: String? = null
var notificationTitle: String? = null
var notificationDescription: String? = null
var notificationIcon: Int? = null
var closeButtonColor: Int? = null
var closeButtonBg: Int? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
videoUrl = intent?.getStringExtra(KEY_STRING_VIDEO_URL)
notificationTitle = intent?.getStringExtra(KEY_STRING_NOTIFICATION_TITLE)
notificationDescription = intent?.getStringExtra(KEY_STRING_NOTIFICATION_DESCRIPTION)
notificationIcon = intent?.getIntExtra(KEY_INT_NOTIFICATION_ICON, -1)
closeButtonColor = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_COLOR, -1)
closeButtonBg = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_BG_COLOR, -1)
return super.onStartCommand(intent, flags, startId)
}
override fun getForegroundNotification(): Notification {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("my_service", "My Background Service")
} else {
packageName
}
var notificationBuilder = NotificationCompat.Builder(this, channelId)
notificationBuilder = notificationTitle?.let {
notificationBuilder.setContentTitle(it)
} ?: run {
notificationBuilder.setContentTitle("Title")
}
notificationDescription?.let {
notificationBuilder = notificationBuilder.setContentText(it)
}
notificationIcon?.let {
notificationBuilder = notificationBuilder.setSmallIcon(it)
}
val notification: Notification = notificationBuilder.build()
return notification
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
override fun getInitialWindowSize(): Point {
return Point(100.toDp(), 100.toDp())
}
override fun getCustomLayoutId(): Int {
return R.layout.pip_layout
}
override fun onServiceRun() {
setOnEventListener(onFullscreen = {
// Not implemented
}, onClosed = {
// Not implemented
})
pipView.removeFullscreenButton()
closeButtonColor?.let {
pipView.getCloseButton().setColorFilter(it)
}
closeButtonBg?.let {
pipView.getCloseButton().setBackgroundColor(it)
}
}
companion object {
const val KEY_STRING_VIDEO_URL = "video_url"
const val KEY_INT_CLOSE_BUTTON_COLOR = "close_button_color"
const val KEY_INT_CLOSE_BUTTON_BG_COLOR = "close_button_background"
const val KEY_STRING_NOTIFICATION_TITLE = "notification_title"
const val KEY_STRING_NOTIFICATION_DESCRIPTION = "notification_description"
const val KEY_INT_NOTIFICATION_ICON = "notification_icon"
}
OverlayPipCustomView
class OverlayPipCustomView : FrameLayout {
private lateinit var constraintsRoot: ConstraintLayout
private lateinit var imageFullscreenButton: ImageView
private lateinit var imageCloseButton: ImageView
private lateinit var customLayoutContent: FrameLayout
private lateinit var customView: View
private lateinit var touchView: View
private lateinit var button: Button
private var playerViewSize: Int = 0
private var sizeChangeable: Boolean = true
private var playerType: Int = 0
private var haveFullscreen = true
/**
* Tracks if viewResizeable is fullscreen.
*/
private var fullscreenOn: Boolean = false
val isDraggable: Boolean
get() {
return !fullscreenOn
}
private var onFullscreen: () -> Unit = {}
private var onClosed: () -> Unit = {}
private var canHideActionButtons = true
private val hideActionHandler = Handler()
private val HIDE_ACTION_DURATION = 2000L
private val hideActionRunnable = Runnable {
if (!isMoving) {
hideActions()
}
}
var isMoving: Boolean = false
constructor(ctx: Context) : super(ctx) {
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet, defStyle: Int) : super(ctx, attrs, defStyle) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
private fun setAttributes(attrs: AttributeSet) {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.OverlayCustomView,
0, 0).apply {
try {
playerViewSize = getInteger(R.styleable.OverlayCustomView_ov_player_size, 0)
playerType = getInteger(R.styleable.OverlayCustomView_ov_size_changeable, 0)
sizeChangeable = getBoolean(R.styleable.OverlayCustomView_ov_size_changeable, true)
} finally {
recycle()
}
}
doOnLayout {
startHideAction()
}
}
private fun initView() {
button = findViewById(R.id.button)
constraintsRoot = findViewById(R.id.constraints_root)
imageFullscreenButton = findViewById(R.id.image_screen_action)
imageCloseButton = findViewById(R.id.image_close)
customLayoutContent = findViewById(R.id.custom_view)
touchView = findViewById(R.id.touch_view)
setListeners()
}
fun addCustomView(view: View) {
customLayoutContent.addView(view)
}
fun addCustomView(layoutId: Int) {
customView = inflate(context, layoutId, null)
customLayoutContent.addView(customView)
}
fun getCloseButton() = imageCloseButton
fun getConstraintsRoot() = constraintsRoot
fun getCustomLayoutContent() = customLayoutContent
fun getCustomView() = customView
fun getFullscreenButton() = imageFullscreenButton
fun getTouchView() = touchView
fun removeFullscreenButton() {
haveFullscreen = false
imageFullscreenButton.invisible()
}
private fun setListeners() {
imageFullscreenButton.setOnClickListener {
onFullscreen.invoke()
}
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
imageCloseButton.setOnClickListener {
onClosed.invoke()
}
}
fun setOnEventActionListener(
onFullscreen: () -> Unit,
onClosed: () -> Unit
) {
this.onFullscreen = onFullscreen
this.onClosed = onClosed
}
private fun startHideAction() {
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun restartHideAction() {
hideActionHandler.removeCallbacks(hideActionRunnable)
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun hideActions() {
if (canHideActionButtons) {
imageCloseButton.invisible()
if (haveFullscreen) {
imageFullscreenButton.invisible()
}
}
}
fun showActions() {
imageCloseButton.visible()
if (haveFullscreen) {
imageFullscreenButton.visible()
}
}
}
In third class I can't understand what should be placed here to to change initial first Activity to another one
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
startService(intent) will try and start a new Service and unfortunately does not throw an error if you call it with an Activity class.
To launch MainActivity2 instead call context.startActivity(intent).

Not able to change language locale in Kotlin

I have 2 string files "en" and "es". When ever I am changing the locale in locale manager class, it saves the new locale successfully but does not reflect changes on refreshing activity.
MyApplication.kt
open class MyApplication : Application() {
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleManagerMew.setLocale(base))
// MultiDex.install(base)
}
override fun onCreate() {
super.onCreate()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
LocaleManagerMew.setLocale(this)
Log.d("app", "onConfigurationChanged: " + newConfig.locale.getLanguage())
}
}
LocaleManagerNew.kt
object LocaleManagerMew {
val SELECTED_LANGUAGE = "MEW_CURRENT_-- USER_LANGUAGE"
var mSharedPreference: SharedPref? = null
var mEnglishFlag = "en"
var mSpanishFlag = "es"
fun setLocale(context: Context?): Context {
return updateResources(context!!, getCurrentLanguage(context)!!)
}
inline fun setNewLocale(context: Context, language: String) {
persistLanguagePreference(context, language)
updateResources(context, language)
}
inline fun getCurrentLanguage(context: Context?): String? {
var mCurrentLanguage: String?
if (mSharedPreference == null)
mSharedPreference = SharedPref(context!!)
mCurrentLanguage = mSharedPreference!!.getSavedLang()
return mCurrentLanguage
}
fun persistLanguagePreference(context: Context, language: String) {
if (mSharedPreference == null)
mSharedPreference = SharedPref(context!!)
mSharedPreference!!.setSavedLang(language)
}
fun updateResources(context: Context, language: String): Context {
var contextFun = context
var locale = Locale(language)
Locale.setDefault(locale)
var resources = context.resources
var configuration = Configuration(resources.configuration)
if (Build.VERSION.SDK_INT >= 17) {
configuration.setLocale(locale)
contextFun = context.createConfigurationContext(configuration)
} else {
configuration.locale = locale
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
}
return contextFun
}
}
BaseActivity.kt
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleManagerMew.setLocale(base))
}
MainActivity.kt
R.id.spanishCL -> {
sp.setSavedLang("es")
var mCurrentLanguage = LocaleManagerMew.getCurrentLanguage(this#MainActivity.applicationContext)
LocaleManagerMew.setNewLocale(this#MainActivity, LocaleManagerMew.mSpanishFlag)
mContext?.recreate()
}
This is the onClick method call to change english locale to spanish. After recreating acitivity new locale changes does not reflect
Try the below code for updating language from attachBaseContext of every Activity
fun setLanguage(context: Context, language: String): ContextWrapper {
var mContext = context
val localeLang = language.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val locale: Locale
if (localeLang.size > 1)
locale = Locale(localeLang[0], localeLang[1])
else
locale = Locale(localeLang[0])
val res = mContext.resources
val configuration = res.configuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val localeList = LocaleList(locale)
LocaleList.setDefault(localeList)
configuration.locales = localeList
mContext = mContext.createConfigurationContext(configuration)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(locale)
mContext = mContext.createConfigurationContext(configuration)
} else {
configuration.locale = locale
res.updateConfiguration(configuration, res.getDisplayMetrics())
}
return ContextWrapper(mContext)
}
And implementation in attachBaseContext of Activity class
// For Language Changing
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase?.let {
Common.setLanguage(
it,
PreferenceManager.getPref<String>(Constants.LANGUAGE_PREFERENCE).toString()
)
})
}
And this is how I restart the activity from fragment
Handler().post {
val intent = activity?.intent
intent?.flags =
Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION
activity?.overridePendingTransition(0, 0)
activity?.finish()
activity?.overridePendingTransition(0, 0)
startActivity(intent)
}

Locale always get reset after onCreate

I try to setting locale in app, first run it works. But, after onCreate() (Rotate) will get reset to default language and I can't change language in app after reset.
I use SharePreference to save locale value
PrefUtils.java to get value from SharePreference
public class PrefUtil {
private static final String PREF_SETTINGS_LANGUAGE = "pref_settings_language";
public static String getLocale(Context context){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String loadLanguage = sharedPreferences.getString(PREF_SETTINGS_LANGUAGE, "");
Log.d("LoadLanguage", loadLanguage);
return loadLanguage;
}
}
MainActivity.java to set locale, I put in onCreate and onResume
private void appLocale(String localeCode){
Locale locale = new Locale(localeCode);
Locale.setDefault(locale);
Configuration configuration = getBaseContext().getResources().getConfiguration();
configuration.locale = locale;
getBaseContext().getResources().updateConfiguration(configuration, getBaseContext().getResources().getDisplayMetrics());
}
In activity class, you should override attachBaseContext(base : Context).
Create an abstract class BaseActivity and extends it on MainActivity.
By doing this you don't have to write onAttachBaseContext() in every activity class but should extend BaseActivity instead of AppCompatActivity.
class BaseActivity : AppCompatActivity {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleHelper.onAttach(base))
}
}
To retain language setting you should also override attachBaseContext(base:Context) in Application class.
class App : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(LocaleHelper.onAttach(base, LocaleHelper.LANG_EN))
}
}
If you need a helper class of localization, here is the one. The code is in Kotlin, but I think you will understand.
object LocaleHelper {
private const val SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"
const val LANG_EN = "en"
const val LANG_ES = "es"
val languageMap = hashMapOf(
LANG_EN to "English",
LANG_ES to "Spanish"
)
fun onAttach(context: Context): Context {
val lang = getPersistedData(context, Locale.getDefault().language)
return setLocale(context, lang)
}
fun onAttach(context: Context, defaultLanguage: String): Context {
val lang = getPersistedData(context, defaultLanguage)
return setLocale(context, lang)
}
fun getLanguage(context: Context): String? {
return getPersistedData(context, Locale.getDefault().language)
}
fun setLocale(context: Context, language: String?): Context {
persist(context, language)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(context, language)
} else {
updateResourcesLegacy(context, language)
}
}
private fun getPersistedData(context: Context, defaultLanguage: String): String? {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getString(SELECTED_LANGUAGE, defaultLanguage)
}
private fun persist(context: Context, language: String?) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = preferences.edit()
editor.putString(SELECTED_LANGUAGE, language)
editor.apply()
}
#TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String?): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.resources.configuration
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
// used for api level < 24
#Suppress("DEPRECATION")
private fun updateResourcesLegacy(context: Context, language: String?): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.resources
val configuration = resources.configuration
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
}

Getting Error for not Initializing Instance of class in my SharedPrefManager class.Not able to solve

SharedPrefManager.kt
class SharedPrefManager private constructor(context: Context) {
lateinit var mCtx: Context
fun isLoggedIn(): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
if (sharedPreferences.getString(KEY_USERNAME, null) != null) {
return true
}
return false
}
fun username(): String {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
return sharedPreferences.getString(KEY_USERNAME, null)
}
fun userEmail(): String {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
return sharedPreferences.getString(KEY_USER_EMAIL, null)
}
init {
mCtx = context
}
fun userLogin(id: Int, username: String, email: String): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putInt(KEY_USER_ID, id)
editor.putString(KEY_USER_EMAIL, email)
editor.putString(KEY_USERNAME, username)
editor.apply()
return true
}
fun logout(): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.clear()
editor.apply()
return true
}
companion object {
lateinit var mInstance: SharedPrefManager
private val SHARED_PREF_NAME = "mysharedpref12"
private val KEY_USERNAME = "username"
private val KEY_USER_EMAIL = "useremail"
private val KEY_USER_ID = "userid"
#Synchronized
fun getInstance(context: Context): SharedPrefManager {
if (mInstance == null) {
line:59-----> mInstance = SharedPrefManager(context)
}
return mInstance
}
}
}
This is the error which i am getting
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.shikh.kotlinmysql, PID: 6600
kotlin.UninitializedPropertyAccessException: lateinit property mInstance has not been initialized
at com.example.shikh.kotlinmysql.SharedPrefManager$Companion.getMInstance(SharedPrefManager.kt:59)
at com.example.shikh.kotlinmysql.SharedPrefManager$Companion.getInstance(SharedPrefManager.kt:66)
at com.example.shikh.kotlinmysql.Login$userLogin$stringRequest$2.onResponse(Login.kt:34)
at com.example.shikh.kotlinmysql.Login$userLogin$stringRequest$2.onResponse(Login.kt:29)
Login.kt
class Login : AppCompatActivity() , View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
buttonLogIn.setOnClickListener(this)
}
private fun userLogin() {
val username = editTextUser.text.toString().trim()
val password = editTextPass.text.toString().trim()
line:29----> val stringRequest = object : StringRequest(Request.Method.POST, Constants.URL_LOGIN,
Response.Listener<String> { response ->
try {
val obj = JSONObject(response)
if (!obj.getBoolean("error")) {
line:34----> SharedPrefManager.getInstance(applicationContext).userLogin(
obj.getInt("id"),
obj.getString("username"),
obj.getString("email")
)
Toast.makeText(applicationContext, "User log in Succesful", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(applicationContext, obj.getString("message"), Toast.LENGTH_LONG).show()
}
} catch (e: JSONException) {
e.printStackTrace()
}
},
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError) {
Toast.makeText(applicationContext, error.message, Toast.LENGTH_SHORT).show()
}
}) {
#Throws(AuthFailureError::class)
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("username", username)
params.put("password", password)
return params
}
}
}
override fun onClick(v: View?) {
if (v==buttonLogIn){
userLogin()
}
}
}
If you're on Kotlin 1.2 you should check if mInstance is initialised like this:
if (!::mInstance.isInitialized) {
mInstance = SharedPrefManager(context)
}

Code Review Best way use SharedPreferences solving MemoryLeak

i try to resolve memory leak in Shared Preferences use, i try all day to do this, but still confuse, my goal is possible call pref in anywhere i want. here my code.
class Preferences (private val context: Context) {
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences(context.packageName+"_pref", Context.MODE_PRIVATE)
private val editor: SharedPreferences.Editor
companion object {
private val KEY_USER = "user"
private val KEY_EMAIL = "email"
}
init {
editor = sharedPreferences.edit()
}
private fun isKeyExist(Key: String): Boolean = sharedPreferences.contains(Key)
private fun putString(Key: String, value: String) {
editor.putString(Key, value)
editor.apply()
}
private fun putInt(Key: String, value: Int) {
editor.putInt(Key, value)
editor.apply()
}
private fun putBoolean(Key: String, value: Boolean) {
editor.putBoolean(Key, value)
editor.apply()
}
private fun putDouble(key: String, value: Double) {
editor.putLong(key, java.lang.Double.doubleToRawLongBits(value))
}
private fun getInt(Key: String): Int = sharedPreferences.getInt(Key, 0)
private fun getString(Key: String): String = sharedPreferences.getString(Key, "")
private fun getBoolean(key: String): Boolean = sharedPreferences.getBoolean(key, false)
private fun getLong(key: String): Long = sharedPreferences.getLong(key, 0)
fun init (){
if(!isKeyExist(KEY_USER)){
putString(KEY_USER,"")
}
if(!isKeyExist(KEY_EMAIL)){
putString(KEY_EMAIL,"")
}
}
private fun resetPref(){
if(isKeyExist(KEY_USER)){
putString(KEY_USER,"")
}
if(isKeyExist(KEY_EMAIL)){
putString(KEY_EMAIL,"")
}
}
var user : String
get() = getString(KEY_USER)
set(value) = putString(KEY_USER,value)
var email : String
get() = getString(KEY_EMAIL)
set(value) = putString(KEY_EMAIL,value)
Because pref need context, i init pref in some class with extend Application like code below,
class BaseApplication : android.app.Application() {
override fun onCreate() {
super.onCreate()
preferences = Preferences(applicationContext)
}
companion object {
#SuppressLint("StaticFieldLeak")
var preferences : Preferences? = null
}
with this method, it's possible to call pref anywhere like activity, fragment, or some class with no context, with this simple way,
BaseApplication.preferences!!.user
but it will make memory leak in my apps.
will appreciate if any someone can give me some advice how to resolve memory leak.
We call this use as a singleton pattern. Use this class :
public class Prefs {
private static final String TAG = "Prefs";
static Prefs singleton = null;
static SharedPreferences preferences;
static SharedPreferences.Editor editor;
private static Gson GSON = new Gson();
Type typeOfObject = new TypeToken<Object>() {
}.getType();
Prefs(Context context) {
preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
editor = preferences.edit();
}
public static Prefs with(Context context) {
if (singleton == null) {
singleton = new Builder(context).build();
}
return singleton;
}
public void save(String key, boolean value) {
editor.putBoolean(key, value).apply();
}
public void save(String key, String value) {
editor.putString(key, value).apply();
}
public void save(String key, int value) {
editor.putInt(key, value).apply();
}
public void save(String key, float value) {
editor.putFloat(key, value).apply();
}
public void save(String key, long value) {
editor.putLong(key, value).apply();
}
public void save(String key, Set<String> value) {
editor.putStringSet(key, value).apply();
}
// to save object in prefrence
public void save(String key, Object object) {
if (object == null) {
throw new IllegalArgumentException("object is null");
}
if (key.equals("") || key == null) {
throw new IllegalArgumentException("key is empty or null");
}
editor.putString(key, GSON.toJson(object)).apply();
}
// To get object from prefrences
public <T> T getObject(String key, Class<T> a) {
String gson = preferences.getString(key, null);
if (gson == null) {
return null;
} else {
try {
return GSON.fromJson(gson, a);
} catch (Exception e) {
throw new IllegalArgumentException("Object storaged with key "
+ key + " is instanceof other class");
}
}
}
public boolean getBoolean(String key, boolean defValue) {
return preferences.getBoolean(key, defValue);
}
public String getString(String key, String defValue) {
return preferences.getString(key, defValue);
}
public int getInt(String key, int defValue) {
return preferences.getInt(key, defValue);
}
public float getFloat(String key, float defValue) {
return preferences.getFloat(key, defValue);
}
public long getLong(String key, long defValue) {
return preferences.getLong(key, defValue);
}
public Set<String> getStringSet(String key, Set<String> defValue) {
return preferences.getStringSet(key, defValue);
}
public Map<String, ?> getAll() {
return preferences.getAll();
}
public void remove(String key) {
editor.remove(key).apply();
}
public void removeAll() {
editor.clear();
editor.apply();
}
private static class Builder {
private final Context context;
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
/**
* Method that creates an instance of Prefs
*
* #return an instance of Prefs
*/
public Prefs build() {
return new Prefs(context);
}
}
}
The kotlin version:
class Prefs internal constructor(context: Context) {
internal var typeOfObject = object : TypeToken<Any>() {
}.type
val all: Map<String, *>
get() = preferences.all
init {
preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
editor = preferences.edit()
}
fun save(key: String, value: Boolean) {
editor.putBoolean(key, value).apply()
}
fun save(key: String, value: String) {
editor.putString(key, value).apply()
}
fun save(key: String, value: Int) {
editor.putInt(key, value).apply()
}
fun save(key: String, value: Float) {
editor.putFloat(key, value).apply()
}
fun save(key: String, value: Long) {
editor.putLong(key, value).apply()
}
fun save(key: String, value: Set<String>) {
editor.putStringSet(key, value).apply()
}
// to save object in prefrence
fun save(key: String?, `object`: Any?) {
if (`object` == null) {
throw IllegalArgumentException("object is null")
}
if (key == "" || key == null) {
throw IllegalArgumentException("key is empty or null")
}
editor.putString(key, GSON.toJson(`object`)).apply()
}
// To get object from prefrences
fun <T> getObject(key: String, a: Class<T>): T? {
val gson = preferences.getString(key, null)
return if (gson == null) {
null
} else {
try {
GSON.fromJson(gson, a)
} catch (e: Exception) {
throw IllegalArgumentException("Object storaged with key "
+ key + " is instanceof other class")
}
}
}
fun getBoolean(key: String, defValue: Boolean): Boolean {
return preferences.getBoolean(key, defValue)
}
fun getString(key: String, defValue: String): String? {
return preferences.getString(key, defValue)
}
fun getInt(key: String, defValue: Int): Int {
return preferences.getInt(key, defValue)
}
fun getFloat(key: String, defValue: Float): Float {
return preferences.getFloat(key, defValue)
}
fun getLong(key: String, defValue: Long): Long {
return preferences.getLong(key, defValue)
}
fun getStringSet(key: String, defValue: Set<String>): Set<String>? {
return preferences.getStringSet(key, defValue)
}
fun remove(key: String) {
editor.remove(key).apply()
}
fun removeAll() {
editor.clear()
editor.apply()
}
private class Builder(context: Context?) {
private val context: Context
init {
if (context == null) {
throw IllegalArgumentException("Context must not be null.")
}
this.context = context.applicationContext
}
/**
* Method that creates an instance of Prefs
*
* #return an instance of Prefs
*/
fun build(): Prefs {
return Prefs(context)
}
}
companion object {
private val TAG = "Prefs"
lateinit var singleton: Prefs
lateinit var preferences: SharedPreferences
lateinit var editor: SharedPreferences.Editor
private val GSON = Gson()
fun with(context: Context): Prefs {
if (singleton == null) {
singleton = Builder(context).build()
}
return singleton
}
}
}
You will need google GSON to save objects.
Calling it is like this :
Prefs.with(context).save("key","value or object or int or boolean");
Hope this helps.

Categories

Resources