0

Overview

I have a game which uses a ViewPager to display many mines which the player can purchase.


Problem

I have created a User Class in order to help me store all user's info in there.

One of these values is gold, which is used to purchase mines. Fact is, I don't know where to create the user (suspect on the MainActivity) and how to access this new user's info from the MineFragment, which is the code of each page in the ViewPager.

Note: I don't want to pass a User object to a fragment or any other classes. I want to be able to make an instance of a single User and then be able to access all of this user's data from anywhere in my code, but in this case, I care about accessing them from the Fragment (Minefragment).


User

public class User {

private String mName;
private int mGold;
private int mExperience;
private int mExperienceLevel;

public User(String name){
    mName = name;
    mGold = 0;
    mExperience = 0;
    mExperienceLevel = 1;
}

// Getters & Setters
[...]

}


MineFragment

public class MineFragment extends Fragment {
// Store instance variables
[...]

private User mUser;

private Button mineUnlockButton;
private View overlay;

[...]

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    [...]

    // HARD CODED VALUES
    mUser = new User("Leonardo");
    mUser.setGold(2000);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(mLayout, container, false);

    [...]

    // Unlock Button
    mineUnlockButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if(mUser.getGold() >= mUnlockCost) {
                overlay.setVisibility(View.GONE); // Remove overlay and button
                mineUnlockButton.setVisibility(View.GONE);
                System.out.println("Gold: " + mUser.getGold() + " | Cost: " + mUnlockCost);
                mUser.setGold(mUser.getGold() - mUnlockCost); // Update user's gold
                System.out.println("Gold: " + mUser.getGold());
                System.out.println("### Mine Purchased ###");

            } else { // Not enough money
                Toast.makeText(getContext(), "Not enough money to purchase", Toast.LENGTH_SHORT).show();
            }
        }
    });

    return view;
}

}


MainFragment

public class MainActivity extends AppCompatActivity {

FragmentPagerAdapter adapterViewPager;
ViewPager viewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    viewPager = (ViewPager) findViewById(R.id.vpPager);
    adapterViewPager = new MineAdapter(getSupportFragmentManager());
    viewPager.setAdapter(adapterViewPager);

}

}

Here's the project on GitHub, where you can find the MainActivity, the User class, the MineFragment and some extra stuff if you need.

FET
  • 942
  • 4
  • 13
  • 35
  • Please provide a [mcve] of the problem in your question. We shouldn't have to go to github to understand what you're asking – OneCricketeer Sep 03 '16 at 14:14
  • Done @cricket_007 – FET Sep 03 '16 at 14:40
  • So you want to pass a User from the Activity to the Fragment as a parameter? Have you tried to make the user object Serializable? And passing it through Fragment arguments? – OneCricketeer Sep 03 '16 at 15:02
  • Possible duplicate of [Passing objects in to Fragments](http://stackoverflow.com/questions/10836525/passing-objects-in-to-fragments) – OneCricketeer Sep 03 '16 at 15:14
  • No, I don't want to pass anything to the Fragment. I want to be able to access the user's data from the fragment, I think Fred_Grott got it right, or at least I think it's closer to what I want to do, I just don't know how to do what he posted. @cricket_007 – FET Sep 03 '16 at 15:22
  • Oh, I see. SharedPreferences, then? Don't need a User object at all, really – OneCricketeer Sep 03 '16 at 15:27
  • Mh, why? This way things wouldn't be cleaner? @cricket_007 – FET Sep 03 '16 at 15:28
  • Because singleton data classes are an anti-pattern in Android (in most cases). You can do it, yes, but you're just storing simple information – OneCricketeer Sep 03 '16 at 15:30
  • I updated my answer, my brain is still not working ;O) but I think it's right – Jon Goodwin Sep 03 '16 at 18:49
  • @JonGoodwin I can't find you answer anymore – FET Sep 04 '16 at 16:23
  • @Fred Grott is playing a joke on you, because you have not learned enough about android java programming yet. I did not want to be associated with his little joke, because he has waited too long to tell you, and it's not funny anymore.Just send me another comment I will undelete the code for you, I'm not sure the site moderators would like this kind of thing. – Jon Goodwin Sep 04 '16 at 16:40
  • What do you mean by "I will undelete the code for you" ?@JonGoodwin – FET Sep 04 '16 at 17:40
  • http://stackoverflow.com/q/39663147/6489557 @cricket_007 – FET Sep 24 '16 at 20:34

3 Answers3

2

You need to use Singleton pattern to store your Data in one Single place.

What I got from your code is there are number of levels in your game and each level requires some minimum number of golds to unlock them.

This is how it look like for test data with 200 gold:-

enter image description here enter image description here

Model classes

Lets start with Level class -lavel have name and minimum gold threashold

    public class LevelModel {

        private String levelName;
        private int unlockCost;

        public String getLevelName() {
            return levelName;
        }

        public int getUnlockCost() {
            return unlockCost;
        }

        public LevelModel(String levelName, int unlockCost) {
            this.levelName = levelName;
            this.unlockCost = unlockCost;
        }
    }

Next will be User class all fields are self explanatory

public class User {
    private String userName;
    private int gold;
    private int experience;
    private int experienceLevel = 1;

    /**
     * @param userName
     * @param gold
     * @param experience
     * @param experienceLevel
     */
    public User(String userName, int gold, int experience, int experienceLevel) {
        this.userName = userName;
        this.gold = gold;
        this.experience = experience;
        this.experienceLevel = experienceLevel;
    }

    //Setters

    public void setGold(int gold) {
        this.gold = gold;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public void setExperienceLevel(int experienceLevel) {
        this.experienceLevel = experienceLevel;
    }

    //Getters

    public String getUserName() {
        return userName;
    }

    public int getGold() {
        return gold;
    }

    public int getExperience() {
        return experience;
    }

    public int getExperienceLevel() {
        return experienceLevel;
    }
}

Now You need a single place to store, update and access your game data here this class will act as Singleton class to hold all your game data.

public class CenterRepository {

    public void setCurrentUser(User currentUser) {
        this.currentUser = currentUser;
    }

    private User currentUser;
    ArrayList<LevelModel> listOfLavels = new ArrayList<>();
    private static CenterRepository singletonInstance;

    private CenterRepository() {
    }

    public static CenterRepository getSingletonInstance() {
        if (null == singletonInstance) {
            singletonInstance = new CenterRepository();
        }
        return singletonInstance;
    }

    public User getCurrentUser() {
        return currentUser;
    }


    public ArrayList<LevelModel> getListOfLavels() {
        return listOfLavels;
    }
}

Now For the second Part how to access and update data from ViewPager . I have enhanced your view pager to use view instead of fragment

MineAdapter Updated**

package com.fet.minebeta.ui;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.fet.minebeta.R;
import com.fet.minebeta.data.CenterRepository;

/**
 * Created by FET on 08/09/2016.
 * All rights reserved.
 * Please contact @fettucciari.leonardo@gmail.com
 */
public class MineAdapter extends PagerAdapter {
    private Context mContext;
    private LayoutInflater mLayoutInflater;

    public MineAdapter(Context context) {
        mContext = context;
        mLayoutInflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {

        //Fill Data directly from Repository
        return CenterRepository.getSingletonInstance().getListOfLavels().size();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == ((FrameLayout) object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, final int position) {

        final View itemView = mLayoutInflater.inflate(R.layout.carousal_page, container,
                false);

        switch (position) {
            case 0:
                itemView.setBackgroundResource(R.color.iron);
                break;
            case 1:
                itemView.setBackgroundResource(R.color.coal);
                break;
            case 2:
                itemView.setBackgroundResource(R.color.gold);
                break;
        }

        //Mine Name
        ((TextView) itemView.findViewById(R.id.mineName)).setText(
                CenterRepository.getSingletonInstance().getListOfLavels().get(position).getmName());

        //Mine Cost
        ((TextView) itemView.findViewById(R.id.mineCost)).setText("" +
                CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost());

        //Mine Cost
        ((TextView) itemView.findViewById(R.id.mineDropRate)).setText("" +
                CenterRepository.getSingletonInstance().getListOfLavels().get(position).getDropRate());

        //Mineral Name
        ((TextView) itemView.findViewById(R.id.mineMineral)).setText(
                CenterRepository.getSingletonInstance().getListOfLavels().get(position).getMineral().getName());

        //Mineral Drop Rate
        ((TextView) itemView.findViewById(R.id.mineDropRate)).setText("" +
                CenterRepository.getSingletonInstance().getListOfLavels().get(position).getMineral().getValue());

        // Unlock Button
        itemView.findViewById(R.id.unlockButton).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (CenterRepository.getSingletonInstance().getCurrentUser().getGold() >=
                        CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()) {

                    //If User has more gold than cost to unlock hide lock image and buy it

                    CenterRepository.getSingletonInstance().getCurrentUser().setGold(
                            CenterRepository.getSingletonInstance().getCurrentUser().getGold()
                                    - CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()); // Update user's gold

                    Toast.makeText(mContext,
                            "Reduced " + CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost() +
                                    "\n Updated Gold " + CenterRepository.getSingletonInstance()
                                    .getCurrentUser().getGold(), Toast.LENGTH_LONG).show();

                } else {

                    // Not enough money
                    Toast.makeText(mContext, "Not enough money to purchase You need " +
                            (CenterRepository.getSingletonInstance().getListOfLavels().get(position).getUnlockCost()
                                    - CenterRepository.getSingletonInstance().getCurrentUser().getGold()) + "More", Toast.LENGTH_SHORT).show();
                }

            }
        });

        container.addView(itemView);

        return itemView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((FrameLayout) object);
    }
}

One thing to note here carousal page have FrameLayout as rootlayout . if you plan to use any other update addview and remove view function accordingly

carousal_page.xml **updated you dont need separate layouts for each mineral

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/mineName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/activity_horizontal_margin"
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:text="COAL MINE"
        android:textColor="@android:color/white"
        android:textSize="25sp" />


    <TextView
        android:id="@+id/mineCost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="5"
        android:gravity="center"
        android:text="1000"
        android:textColor="@android:color/white"
        android:textSize="50sp" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="3"
        android:paddingEnd="100dp"
        android:paddingStart="100dp">

        <TextView
            android:id="@+id/mineMineral"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignTop="@+id/mineDropRate"
            android:text="COAL"
            android:textAlignment="center"
            android:textColor="@android:color/white"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/mineDropRate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:text="1"
            android:textAlignment="center"
            android:textColor="@android:color/white"
            android:textSize="25sp" />

    </RelativeLayout>
</LinearLayout>

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/unlockButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Unlock" />

</RelativeLayout>

Model class is so loosely coupled that you can move it anywhere and it work just fine.

Activity Class with test Data Updated**

public class MainActivity extends AppCompatActivity {

    PagerAdapter adapterViewPager;
    ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //  Add Test User from Activity
        CenterRepository.getSingletonInstance().setCurrentUser(new User("FET", 200, 20, 10));

        //Add Test Mines
        CenterRepository.getSingletonInstance().getListOfLavels().add(new Mine("Iron", new Mineral("Iron Mineral", 1), 100, 2));
        CenterRepository.getSingletonInstance().getListOfLavels().add(new Mine("Coal", new Mineral("Coal Mineral", 3), 200, 2));
        CenterRepository.getSingletonInstance().getListOfLavels().add(new Mine("Gold", new Mineral("Gold Mineral", 2), 300, 2));

        viewPager = (ViewPager) findViewById(R.id.vpPager);
        viewPager.setAdapter(new MineAdapter(this));

        Toast.makeText(getApplicationContext(), "Current Credits " + CenterRepository.getSingletonInstance()
                .getCurrentUser().getGold(), Toast.LENGTH_LONG).show();


    }


}
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
  • Wow, thank you so much for the detailed answer! I just don't get how should I instantiate the adapter, what's the resource ID? And how I should setup the whole code, as I've basically made all you said t do, but I can't see the mines, where should I create them? – FET Sep 08 '16 at 21:30
  • remove the layout id parameter. Adpater is inflating data stored in listOfLavels of CenterRepository you need to fill lisOfLavels with some dummy data and adpter will work just fine. – Hitesh Sahu Sep 09 '16 at 03:04
  • Mmmm done. But now, as you said you swapped fragments with views, how do I edit each view's layout? Before I had 3 different layouts in the layout folder, now what? – FET Sep 09 '16 at 10:47
  • View pager is adding view in instantiate item. You can find views by item view.findViewById(R.id.viewid). See how it can be done http://stackoverflow.com/questions/18710561/can-i-use-view-pager-with-views-not-with-fragments – Hitesh Sahu Sep 09 '16 at 15:48
  • this singletone approach is not synchronized, thus not dispatched. This doesnt guarantee to have only one instance, if you call getSingletonInstance() from two places, in the SAME time, it may produce two instances. – Josef Korbel Sep 11 '16 at 12:26
  • 1
    I still need to test, but your answer is the most full here and very detailed, so I'm giving you the bounty but I'll leave you here a reply when I test it out! @HiteshSahu – FET Sep 12 '16 at 08:52
  • @HiteshSahu I'm sorry, I still didn't understand how I'm supposed to add tabs and more in particular different XML layouts to each one – FET Sep 12 '16 at 17:36
  • See updated answer I have enhanced it for you and it is fully working. – Hitesh Sahu Sep 13 '16 at 10:57
  • It works now. I've added the line of code to make the unlock button disappear when the user has bought the mine, but the fact is everytime I swap to another mine and then come back into that previously bought one, the button comes back infact I've tested the code and it runs several times the insantiateItem method, this way the button gets re created everytime, I think the setup should run before this or outside of this method anyway @HiteshSahu – FET Sep 13 '16 at 16:52
  • Just noticed it changes when I click an unlock button, so each time I have just ONE button as GONE – FET Sep 13 '16 at 16:54
  • Hey @HiteshSahu any ideas? – FET Sep 15 '16 at 19:41
  • @HiteshSahu You there? – FET Sep 23 '16 at 13:31
1

It is not a bad start...

Since that will probably your only game initialization class, you need to make it singleton. One of the tricks in java is that if you change the class to enum and make a field called INSTANCE

to use in your MainActivity in your onCreate call

User.INSTANCE

That will initialize the game with the default values you have in the User class. Than you have access to all the methods of the User class and fields.

Fred Grott
  • 3,505
  • 1
  • 23
  • 18
0

In Your User Class Create A Static User Which Represents The Person Who is Playing The Game:

    public class User {

    public static User user = new User("Name Here");

    private String mName;
    private int mGold;
    private int mExperience;
    private int mExperienceLevel;

    public User(String name){
        mName = name;
        mGold = 0;
        mExperience = 0;
        mExperienceLevel = 1;
    }
    // Getters & Setters
    [...]

Access it with eg:

    User.user.setGold(User.user.getGold() + 1);
obwan02
  • 113
  • 10