How can I have access to a file in my res/raw folder from a Singleton (not an activity) on Android?
I've tried:
InputStream is = MainActivity.getResources().openRawResource("data.json");
which doesn't work since "non-static method getResouces() cannot be referenced from static content".
I've also tried:
URL fileURL = getClass().getClassLoader().getResource(R.raw.data);
String filePath = fileURL.getPath();
which throws a Null-pointer exception.
My Singleton:
public class CoursesDataManager {
private static CoursesDataManager instance;
private final List<Course> courses;
public static CoursesDataManager getInstance() {
if (instance == null)
instance = new CoursesDataManager();
return instance;
}
private CoursesDataManager() {
courses = parseCourses(**filePath/inputStream**);
}
The reason I want to get the file is that I want my Singleton to parse the data in that file once, store this data, and have this data never change and be accessible throughout the lifetime of my application.
Thanks a lot.
public class CoursesDataManager {
private static CoursesDataManager instance;
private final List<String> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
instance = new CoursesDataManager(context);
return instance;
}
private CoursesDataManager(Context context) {
courses =context.getResources().openRawResource(R.raw.filename);
}
}
call it from Activity
CoursesDataManager.getInstance(getApplicationContext());
You need context to access resources. You could use Application context for this purpose. Subclass your Application, save the context in a static variable. Use the context inside singleton.
Create a Application class:
public class MyApplication extends Application {
public static MyApplication context = null;
public void onCreate() {
super.onCreate();
context = this;
}
}
In your manifest, specify the name of the Application class:
<application
android:name=".MyApplication"
...
>
</application>
Now, use this context in singleton:
InputStream is = MyApplication.context.getResources().openRawResource("data.json");
Note: You can use this method, even when you are creating the singleton instance from outside the Activity, where you dont have Activity context.
Add the context to your class and change it to something like that:
public class CoursesDataManager {
private static CoursesDataManager instance;
private Context context;
private final List<Course> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
instance = new CoursesDataManager(context.getApplicationContext());
return instance;
}
private CoursesDataManager(Context context) {
this.context = context;
courses = parseCourses(**filePath/inputStream**);
}
public class CoursesDataManager {
private static CoursesDataManager instance;
private static Context mContext;
private final List<Course> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
{
instance = new CoursesDataManager();
}
mContext = context;
return instance;
}
private CoursesDataManager() {
courses = parseCourses(**filePath/inputStream**);
}
You can use resource from here like
mContext.getResources().openRawResource(resourceId);
Related
I want to do a core api service integration in my Android project.
My core service class is as follows:
public class TestApplication extends MultiDexApplication {
private static final String LOG_TAG_NETWORK = "GNetwork";
public static JacksonConverterFactory factory;
private static TestApplication instance;
private OkHttpClient.Builder okHttpBuilder;
private TestService testtService;
private Location lastKnownLocation;
public static TestApplication getInstance() {
return instance;
}
#Override
public void onCreate() {
super.onCreate();
instance = this;
Pref.init(this);
okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.connectTimeout(UrlConstants.CONNECT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.readTimeout(UrlConstants.READ_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.writeTimeout(UrlConstants.WRITE_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
if (BuildConfig.LOG_ENABLED) {
HttpLoggingInterceptor httpLoggingInterceptor =
new HttpLoggingInterceptor(message -> Log.d(LOG_TAG_NETWORK, message));
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpBuilder.addInterceptor(httpLoggingInterceptor);
}
factory = getJacksonConverterFactory();
}
#NonNull
private JacksonConverterFactory getJacksonConverterFactory() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return JacksonConverterFactory.create(objectMapper);
}
public void setupRetrofit() {
String BASE_URL = "http://xxxxx";
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(okHttpBuilder.build())
.addConverterFactory(factory).build();
testService = retrofit.create(TestService.class);
}
public TestService getTestService() {
return testService;
}
}
Then I create an instance from this core class. But here my constant getIntance value returns null.
TestApplication.getInstance().setupRetrofit();
I can't understand that. GetInstance method always return null;
In my app build.gradle file is multiDexEnabled true.
How to solve this situation?
never use instance of application like this. for get application instance you need activity and in your activity you can use
static TestApplication getInstance(Activity activity) {
if (instance == null)
instance = ((TestApplication) activity.getApplication());
return instance;
}
this will solve your issues but the way you implement retrofit and using it is not best practices. You can find better way that doesn't need activity.
change getInstance()
from this
public static TestApplication getInstance() {
return instance;
}
to this
public static TestApplication getInstance() {
instance=new TestApplication();
return instance;
}
and also remove this line from oncreate
instance = this;
After Enabling Multidex
Replace With:-
public class Application extends MultiDexApplication {
public static final String TAG = Application.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
ApplicationHelper.initDatabaseHelper(this);
PostInteractor.getInstance(this).subscribeToNewPosts();
}
}
I'm trying to create a singleton class that will be responsible for playing game sounds. I created a singleton class GameSounds with a method playSound(). In the res folder I have a a subfolder 'raw' with a file letter_found.mp3.
This is the source code of the GameSounds class I wrote:
import android.app.Application;
import android.content.Context;
import android.media.MediaPlayer;
public class GameSounds extends Application {
private static GameSounds gameSounds = new GameSounds();
private static MediaPlayer soundPlayer;
private static Context mContext;
private static int mySoundId = R.raw.letter_found;
private GameSounds() {
mContext = this;
}
public static GameSounds getInstance() {
return gameSounds;
}
public static void playSound() {
soundPlayer = MediaPlayer.create(mContext, mySoundId);
soundPlayer.start();
}
}
This doesn't seem to work as I'm getting the following error message:
"java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference"
I don't understand why this is happening. I tried to search Stackoverflow but couldn't find a solution.
Any help/explanation is greatly appreciated.
You can have a Singleton holding an Application Context (NOT Activity context) but practically you have to set this context before you use your singleton which can be enforced by throwing exceptions. See below example code.
public class GameSounds {
private static Context sContext;
public static void setContext(Context context) {
if (context == null) {
throw new IllegalArgumentException("context cannot be null!");
}
// In order to avoid memory leak, you should use application context rather than the `activiy`
context = context.getApplicationContext();
if (context == null) {
throw new IllegalArgumentException("context cannot be null!");
}
sContext = context;
}
private static Context getContext() {
if (sContext != null) {
return (Context)sContext;
}
throw new IllegalStateException("sContext was not set yet! Please call method setContext(Context context) first.");
}
// the rest of other methods. e.g. playSounds()
private static GameSounds gameSounds = new GameSounds();
private GameSounds() {
}
public static GameSounds getInstance() {
return gameSounds;
}
public void playSound() {
Context context = getContext();
soundPlayer = MediaPlayer.create(context, mySoundId);
soundPlayer.start();
}
}
You shouldn't inherit Application class unless you try to use Singleton pattern. Because Application is base class which contains all other components such as activities and services.
Instead, GameSound class should contain Context object and proper constructor.
Example)
public class GameSounds {
private GameSounds gameSounds;
private MediaPlayer soundPlayer;
private WeakReference<Context> mContext;
private int mySoundId = R.raw.letter_found;
private GameSounds(Context context) {
mContext = new WeakReference<>(context);
}
public GameSounds getInstance(Context context) {
if (gameSounds == null) {
gameSounds = new GameSounds(context);
}
return gameSounds;
}
public void playSound() {
soundPlayer = MediaPlayer.create(mContext.get(), mySoundId);
soundPlayer.start();
}
}
In this code, there is WeakReference<Context> instead of Context. WeakReference is used to prevent memory leaks because memory leaks can occur if you have an instance outside the activity.
To play sound, execute GameSounds.getInstance(this).playSound(); is fine.
If Context can't provide when try to play sound, implement initialize methods and called in Application class can be ok.
public class GameSounds {
private static GameSounds gameSounds;
private MediaPlayer soundPlayer;
private WeakReference<Context> mContext;
private int mySoundId = R.raw.letter_found;
private GameSounds(Application context) {
mContext = new WeakReference<>(context);
}
public static void initialize(Application context) {
if (gameSounds == null) {
gameSounds = new GameSounds(context);
}
}
public static GameSounds getInstance() {
if (gameSounds == null) {
throw new NullPointerException("You need to initialize this code by GameSound.initialize(this) in application class");
}
return gameSounds;
}
public void playSound() {
soundPlayer = MediaPlayer.create(mContext.get(), mySoundId);
soundPlayer.start();
}
}
In this case, you should make Application class and initialize GameSound class by GameSound.initialize(this) in Application class.
To play sound, GameSound.getInstance().playSound() is fine.
For instance I want to take this
public class SomeClass {
public static final String GREET_STRING = "Hello!";
//...
and change it to something like:
public class SomeClass {
public static final String GREET_STRING = getString(R.string.greet_string);
//...
Can this be done or do I need some kind of Context instantiation to get the resources for the string loading?
To use getString() you will need a context. A Resource string cannot be static final because it is possible for String resources to change as you change Locales (if you have multiple String files such as strings.xml (us) and strings.xml (uk))
Try this:
public abstract class SomeClass extends AppCompatActivity {
public static String GREET_STRING(Context context) {
if (context == null) {
return null;
}
return context.getResources().getString(R.string.greet_string);
}
}
Res/Value/String:
<resources>
<string name="greet_string">Hello!</string>
</resources>
Call SomeClass from MainClass
public class MainClass extends SomeClass {
private final static String TAG = MainClass.class.getName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// call SomeClass from MainClass
Log.i(TAG, SomeClass.GREET_STRING(this));
}
}
There are two ways access the string inside the class which is not extending Activity or Fragment.
Pass Context or Activity to class constructor
public class SomeClass {
private Context context;
public SomeClass(Context context) {
this.context = context;
}
public static final String GREET_STRING = context.getString(R.string.greet_string);
}
The second way is if you don`t want to pass context to class. You need to create an instance of the Application and static function get instance.
public class App extends Application {
private static App instance = null;
#Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static App getInstance() {
// Return the instance
return instance;
}
}
public class SomeClass {
public static final String GREET_STRING = App.getInstance().getString(R.string.greet_string);
}
I need to have have reference to Context in my utils class.
First I am extending Application class and initializing my util class:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Utils.init(getApplicationContext());
}
}
And utils class looks like:
public class Utils{
private static Context sContext;
private Utils() {
}
public static void init(Context context) {
sContext = context;
}
}
Is there any possible way to get a leak with such approach?
I can see only one case: when application goes background - Context can be re-created, and so Utils class may be re-initialized even if it will persist in memory.
Any suggestions, please.
You should solve as follow:
public class YourClass extends Application {
private static Context context;
public void onCreate()
{
super.onCreate();
YourClass.context = getApplicationContext();
}
public static Context getAppContext() {
return YourClass.context;
}
}
How to use:
YourClass.getAppContext();
I've write a Singleton, but this singleton need a Context as a param to initialize itself. As the Context is used only once in its constructor, I would not like to add it in getInstance(Context). After thinking more, I came out the following answer:
public class Singleton {
private static Context sContext;
public static void init(Context context) {
sContext = context;
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() {
if (sContext == null) {
throw new IllegalArgumentException("#init should be called in Application#onCreate");
}
// Initialize the Singleton.
// .....
// After the constructed, remove the sContext.
sContext = null;
}
}
It's well, with a class method init called in Android/Applicaiton#onCreate method.
It's not instance the SingletonHolder.INSTANCE, as it's not loaded.
Could some give someone advice on my solution。Thanks!
With the help of # WarrenFaith I changed my code.
public class Singleton {
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() {
final Context context = BaseApplication.getApplication();
// Initialize the Singleton.
// .....
}
}
public class BaseApplication extends Application {
private static Application sApplication;
public static Application getApplication() {
return sApplication;
}
#Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
}
Why not using a way easier solution:
public class Singleton {
private final static Singleton mInstance = new Singleton();
private final static Context sContext;
private Singleton() {
sContext = MyApplication.getInstance();
// do more
}
public static Singleton getInstance() {
return mInstance;
}
}
That is a pretty bullet proof singleton pattern.
Of course you need to implement your application class to be a singleton but by definition it already is.
public class MyApplication extends Application {
private static MyApplication mInstance;
#Override
protected void onCreate() {
super.onCreate();
mInstance = this;
// create your Singleton!
Singleton.getInstance();
}
public static MyApplication getInstance() {
return mInstance;
}
}