Getting raw resource (sound) in non-activity class - java

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.

Related

How to take R.string value instead of hardcoded value, if it's a class field?

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);
}

Singleton to get a file location on Android

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);

Android - static Context

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();

Singleton with params

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;
}
}

Android singleton becomes null

I have a singleton class which I first instantiate in 1 activity, then use it in 2 other activities. When the 2nd activity loads, the instance becomes null, where is was not null before.
Here is the code for the singleton class:
public class RestaurantMenuHolder{
private static RestaurantMenuHolder mInstance = null;
public static ArrayList<RestaurantMenuObject> restaurantMenu;
public static RestaurantMenuHolder getInstance(ArrayList<RestaurantMenuObject> restaurantMenu){
if(mInstance == null){
mInstance = new RestaurantMenuHolder(restaurantMenu);
}
return mInstance;
}
public static RestaurantMenuHolder getInstance(){
return mInstance;
}
private RestaurantMenuHolder(ArrayList<RestaurantMenuObject> restaurantMenu){
this.restaurantMenu = restaurantMenu;
}
public static int getCount(){
return restaurantMenu.size();
}
}
I have tried adding synchronized to the getInstance methods, the constructor and getCount, but I still get null when the second activity loads. Does anyone know why or how the singleton would suddenly become null?
I can provide code for the activities if required.
EDIT: More Code
When I initialize the singleton: I run holder = RestaurantMenuHolder.getInstance(restaurantMenu); where restaurantMenu is an arrayList. I initialize this to store data. This is verified to work, the singleton is not null at this point.
In the first activity that I use the singleton, I run RestaurantMenuHolder menuHolder = RestaurantMenuHolder.getInstance(); in onCreateView for the fragment. I use this instance to retrieve the data previously stored. The singleton is also verified to work here.
When the second activity is started, the singleton becomes null, but not immediately. I run menuHolder = RestaurantMenuHolder.getInstance(); again in the second activity and retrieve some more data, all of which is valid.
The problem appears to occur in the ImageAdapter. I use a slidingMenu with an ImageAdapter for both activities. The singleton works for the first activity and is not null, but becomes null when I try to use it again in the second activity.
Here is code from the ImageAdapter:
public class ImageAdapter extends BaseAdapter{
private static LayoutInflater inflater = null;
private ViewHolder holder;
private String id;
private RestaurantMenuHolder menuHolder;
private RestaurantLocationHolder locationHolder;
private Context context;
private String[] sliding_list = {"Home", "Skip to Menu", "Location & Hours", "About Us"};
Typeface tf;
public ImageAdapter(Context context, String id){
this.context = context;
inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.id = id;
tf = Typeface.createFromAsset(context.getAssets(),
"Roboto-Light.ttf");
menuHolder = RestaurantMenuHolder.getInstance();
locationHolder = RestaurantLocationHolder.getInstance(context);
MenuUtilities.setImageHash();
}
#Override
public int getCount() {
if(id == "menu"){
Log.d((menuHolder == null) + "", "MENUHOLDER NULL");
return menuHolder.getCount();
} else if (id == "location") {
return locationHolder.getCount();
} else {
return sliding_list.length;
}
}
... more code
When the second activity is started, return menuHolder.getCount(); results in a NullPointerException. Log.d((menuHolder == null) + "", "MENUHOLDER NULL"); returns true at this point, when it had previously returned false.
just Do single change and will work
public class RestaurantMenuHolder{
private static RestaurantMenuHolder mInstance = null;
public static ArrayList<RestaurantMenuObject> restaurantMenu;
public static RestaurantMenuHolder getInstance(ArrayList<RestaurantMenuObject> restaurantMenu){
if(mInstance == null){
mInstance = new RestaurantMenuHolder(restaurantMenu);
}
return mInstance;
}
public static RestaurantMenuHolder getInstance(){
if(mInstance == null){
mInstance = new RestaurantMenuHolder(restaurantMenu);
}
return mInstance;
}
private RestaurantMenuHolder(ArrayList<RestaurantMenuObject> restaurantMenu){
this.restaurantMenu = restaurantMenu;
}
public static int getCount(){
return restaurantMenu.size();
}
}

Categories

Resources