I'm making my first android app and here's where i'm stuck.
I have an activity A which requires 4 players to be picked.
I'm passing to the activity PickPlayer 1,2,3,4 according to which player i want to fill.
ImageButton addp1 = (ImageButton)findViewById(R.id.player1);
addp1.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Intent i = new Intent(getApplicationContext(), PickPlayer.class);
i.putExtra("playersList", playersList);
startActivityForResult(i, 1);
}
});
On the PickPlayer activity i have a list which is populated and each item receives a listener.
final ArrayList<Player> playersList = (ArrayList<Player>)getIntent().getSerializableExtra("playersList");
lv.setAdapter(new PlayerItemAdapter(this, android.R.layout.simple_list_item_1, playersList));
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long arg) {
player = playersList.get(position);
playersList.remove(position);
Intent intentMessage = new Intent();
intentMessage.putExtra("player", player);
intentMessage.putExtra("playersList", playersList);
setResult(RESULT_OK, intentMessage);
finish();
}
});`
The above works fine by creating the playersList on activity A and passing it through from one to another each time and removing the player from the playerList on click.
Problem is if a player is chosen by mistake he needs to be put back into the list again once replaced by someone else.
Any suggestions on implementing this ?
One way i thought of is to pass from activity A to PickPlayer the player ( if one is assigned already at his position ) and readding him to the playerList again but i'm sure there's a better way for it.
I'm new to android so i have no idea about resources and best practises.
( example passing an object through activities or an id and run a db query).
Thanks
IMO, you'll achieve best results with a singleton class (or methods on Application instance - here a good stackoverflow question about it).
Your array would be an internal member of the singleton and have an boolean attribute to indicate if a player is already picked or not. Some methods using this attribute can be implemented like:
List<Player> getPickedPlayers()
List<Player> getNotPickedPlayers()
void setPlayerPicked(Player player)
void setPlayerNotPicked(Player player)
and so on...
Hope it helps!
When you send object through an Intent's bundle (i.putExtra("playersList", playersList);), it is marshalled and then unmarshalled on the other side (the new activity). This mean you have 2 instances of ArrayList and its content (one in each activity). If you wish to share data between activity A and activity B, I suggest you store it on an Application instance or by using a singleton.
If your data is coming from a database, you can pass the id through the intent, and get the list of players and the special player with a database query.
Not sure if this is the best way to accomplish this but i'm going to share with you.
I move the arrayList with the players back and forth in between the activities.
Once the player is sent back it's removed from it and kept in an object player1,player2,player3 etc etc.
So if the user clicks the button that has already a player assigned to it i simply add that player again into the list and pass the arrayList as i would do if it was empty.
Related
I'm trying to do a written report on some code and I found one on youtube. However I don't understand what is going on exactly. I understand that it get's the ID of a certain object which then opens in a new Java class but if someone could breakdown what is happening it would be greatly appreciated.
private void setUpOnclickListener()
{
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l)
{
Supplier selectSupplier = (Supplier) (listView.getItemAtPosition(position));
Intent showDetail = new Intent(getApplicationContext(), DetailActivity.class);
showDetail.putExtra("id",selectSupplier.getId());
startActivity(showDetail);
}
});
}
at first read some doc, what IS an Activity, then read how to start new one, and why Intent is needed for this
in short: this Intent is carrying information what dev want to start/open (will start new "window" - DetailActivity) and what will be passed to this component (some id)
The code gets the supplier that is clicked on in the listview in order to gain their supplier id. That ID is then given to the DetailActivity page using an intent, whereby it will be used to display the details that relate to the specific supplier. On the DetailActivity page, the intent and its extra information, namely the supplier id, will be retrieved, and then can be used to display the details for the specific supplier that was clicked on on the previous page.
I have realm objects with Primary keys here's an example:
#RealmClass
public class Person extends RealmObject(){
#PrimaryKey
Int personID;
String name;
Int age;
}
The Person onjects are viewed in a recycler view, where the viewholder has an OnClick method. The OnClick method is fully functional, in the sense that screens change and everything inside it executes. However the Data from the specific Person object selected does not pass through.
Here is the code inside the OnClick method:
Intent intent = new Intent(this, PersonStats.class);
intent.putExtra("I don't know what to put here!");
startActivity(intent);
Where PersonStats is the class that will show all the stats of the Person object selected from the recycler view. I've been messing around with the intent.putExtra function but I can't get it to work. How would I go about this?
Edit: I'm asking for how to get the specific Realm Object that I selected from the recycler view so I can pass it through putExtra. As RecyclerView postion != PrimaryKey.
sorry if this is a convoluted question. Working on creating an app for a college course and I'm running into (what appears to be) a race condition in my OnCreate method.
TL;DR - sometimes my spinner populates and I can get an index from it. Sometimes it's not populated yet when trying to get a specific index. Details and code below.
The app is a "course scheduler" for a college student.
I'm creating an Activity that displays existing course information and allows you to edit it. In the OnCreate method for this Activity, I am filling a spinner for "Mentors" for the course and a spinner for which "Term" the course belongs in. This information is being pulled from a Room DB.
I have a seperate activity for a new course and for editing a course. For the "new course" activity, everything works fine. I getAllMentors() or getAllTerms() successfully and fill the spinner list.
For the "Edit Course" Activity, there's an extra step involved and it seems to be causing me some issues.
When editing a course, I pass the intent from the originating Activity with all the necessary EXTRAS. This is successful.
In OnCreate for EditCourseActivity, I do the following:
I get the mentorID from the EXTRA that's passed in from the originating Activity.
I access my MentorViewModel and call my getAllMentors() method which returns LiveData> of all mentors in the db.
because it returns LiveData, I use an observer and loop through the LiveData adding the Name of each mentor to a List and the
entire mentor to a List.
I populate my spinner with the information in List full of mentor names.
then I do a for loop, looping through List looking for one that has the same id as what I grabbed form the EXTRA in step 1.
If I find a match in that list, I call a getMentorName() method to snag their name as a string.
I have a methond getIndex(spinner, string) that will loop through the provided spinner, trying to find a match for the string that's
passed in (mentors name) that I grabbed that should match the ID of
the mentor assigned to the course. This method returns index location
of the matched string in the spinner.
I set the spinner selection to the index found.
I do basically the same process for term.
Me being a new developer, I'm not used to OnCreate running the code synchronously.
Because of this, it appears that I have a race condition somewhere between populating the List of mentor names that populates the spinner, and calling my getIndex() method.
Sometimes the spinner is populated and getIndex works properly and sets the correct mentor. Sometimes the spinner is empty and my getIndex() returns -1 (which it should do in a no-find situation) that populates the spinner with the first item in the list (once it's populated).
protected void onCreate(Bundle savedInstanceState) {
//////////////////////////Handling Mentor spinner menu/////////////////////////////////////////////////
int mentorId = courseData.getIntExtra(EXTRA_COURSE_MENTOR_ID, -1);
final ArrayAdapter<String> sp_CourseMentorAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mentorNameList);
sp_CourseMentorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp_CourseMentor.setAdapter(sp_CourseMentorAdapter);
final MentorViewModel mentorViewModel = ViewModelProviders.of(this).get(MentorViewModel.class);
//Mentor test = mentorViewModel.getMentorById(mentorId);
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
}
sp_CourseMentorAdapter.notifyDataSetChanged();
}
});
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
Is there a way to get them to run asynchronously? Somehow to get the observer doing my getAllMentors() to complete first and populate the spinner, THEN have the for loop run?
Or a better way to handle this?
Thanks in advance.
Room always runs the code on a separated thread, not the Main/UI thread. You can change that behavior with
allowMainThreadQueries()
after initializating your database. This will make the query run first, populate your list and then run your for-loop code. I do not recommend this approach, since it is a bad practice to make queries on the UI thread.
You have two options:
Change your foor loop to a function and call it after adding the values from the observer:
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
lookForMentor();
}
}
});
private void lookForMentor() {
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
Put the for inside the observer, change the Room DAO to return a List and use LiveData on your own viewmodel:
MentorViewModel.java:
MentorViewModel extends ViewModel {
private MutableLiveData<List<Mentor>> _mentorsLiveData = new MutableLiveData<List<Mentor>>();
public LiveData<List<Mentor>> mentorsLiveData = (LiveData) _mentorsLiveData;
void getAllMentors(){
//room db query
_mentorsLiveData.postValue(mentorsList);
}
}
EditActivity.java:
mentorsViewModel.getAllMentors();
mentorViewModel.mentorsLiveData.observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
mentorsListMentor.addAll(mentorList);
sp_CourseMentorAdapter.notifyDataSetChanged();
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
}
});
I am making a frisbee logger and have an ArrayList of Team objects. Each Team has an ArrayList of Player objects. Everything is using Serializable properly to be sent using Intent.
In my main activity I am displaying the list of Team objects in a ListView and an option to add another Team (only a name is needed). Once a Team is selected I pass the object to another activity using Intent. On this second activity I have it display the list of Player objects and have fields to enter another player object into the passed list.
When I return to the main activity and go back to the add Player activity, what I have added is gone.
I cannot use static because there is obviously more than one Team object. I think passing back the changed ArrayList could work but that seems a little lame, time-consuming, and frustrating.
Is there a built-in way in Android Studio that does this or am I on my own?
Note: I am not using SQLite as suggested in the comments
There's not a whole lot to show on this but here it is I guess:
MainActivity.java
private static ArrayList<Team> listOfTeams = new ArrayList<>();
private static ArrayList<Game> listOfGames = new ArrayList<>();
private ListView gameList, teamList;
.....
teamList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Team t = (Team)teamList.getItemAtPosition(position);
viewTeam(t);
}
});
.....
//Item select in teamList. Start the TeamViewActivity
public void viewTeam(Team t)
{
Intent i = new Intent(this, TeamViewActivity.class);
i.putExtra("teamView",t);
startActivity(i);
}
TeamViewActivity.java
private Team team;
private ListView rosterList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_team_view);
rosterList = (ListView) findViewById(R.id.playerList);
Intent i = getIntent();
Bundle extras = i.getExtras();
if(extras!=null)
{
if(extras.get("teamView")!=null)
{
team = (Team) extras.get("teamView");
}
}
populateRosterList(team.getRoster());
}
public void addPlayerToRoster(View view)
{
String checkFirst = ((EditText) findViewById(R.id.firstText)).getText().toString();
String checkLast = ((EditText) findViewById(R.id.lastText)).getText().toString();
String checkNumber = ((EditText) findViewById(R.id.numberText)).getText().toString();
if(!checkNumber.equals(""))
{
team.addPlayer(checkFirst, checkLast, Integer.parseInt(checkNumber));
((EditText) findViewById(R.id.firstText)).setText("");
((EditText) findViewById(R.id.lastText)).setText("");
((EditText) findViewById(R.id.numberText)).setText("");
populateRosterList(team.getRoster());
}
}
public void returnToMain(View view)
{
Intent i = new Intent(this, MainActivity.class);
i.putExtra("teamView", team);
startActivity(i);
}
private void populateRosterList(ArrayList<Player> list)
{
ArrayAdapter<Player> adapter = new ArrayAdapter<>(this,
R.layout.activity_list, R.id.genericText, list);
rosterList.setAdapter(adapter);
}
Consider your concept:
You serialize an object, i.e. you transform it into a transferrable format which is then copied over to the other activity and reconstructed as a new instance.
Consequently, you alter another instance, which is not available in the previous activity, if you do not return it - again, serialized - and finally reconstruct and copy it back into the respective instance.
What you need is a shared memory storage in your application, which can alter and retrieve data cross-activity OR a proper data routing using Intents w/ ISerializable.
Options:
Always serialize objects and pass and copy them around.
-> No multithreaded alteration, possibly slow, unbeautiful
Singleton application with global data storage ir Context Object (I do NOT recommend the due to memory management and Garbage
Collection inbetween Activity Switches BUT for consistency I'd
wanted to mention this option)
SQLite3
-> Quick, Simple and Scalable, But a bit cumbersome to get started with
Any other file-structure stored and maintained in the data folder
-> I'd expect a lot of boilerplate code here, and low performance
Webservice and remote database
Proper component setup, i.e. initialize all accessing components in your software with the appropriate reference to the data structs using for example fragments (Thanks to #mismanc, I actually missed that option initially)
In general you could abstract all that away using services and repositories, which allows you to under-the-hood test options like 3. 4. And 5. and find your best solution, and in addition, keeo the accessing code simple and clean.
in your case, you can use startActivityForResult instead of startActivity, then get your modified Team object from onActivityResult(int requestCode, int resultCode, Intent data) to update your list.
startActivityForResult example
You can use fragments. You hold the list in the MainActivity and pass its reference to ShowListFragment and AddPlayerFragment by interfaces. And you can also do other operations over them. If you dont want to use json or sqlite it can be a good way for you.
MainActivity.java
public class MainActivity extends Activity implements ShowListener{
public interface ShowListener{
ArrayList<Team> getTeamList();
}
private ArrayList<Team> listOfTeams = new ArrayList<>();
#Override
public ArrayList<Team> getTeamList() {
return listOfTeams;
}
}
ShowListFragment.java
public class ShowListFragment extends Fragment {
private ArrayList<Team> listOfTeams;
private ShowListener listener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listener = (ShowListener)getActivity();
listOfTeams = listener.getTeamList();
}
}
As #Kingfisher Phuoc mentioned you could use srartActivityForResult in case you don't want to change your approach.
Otherwise I will suggest you use either :
SharedPreference to store your arraylist object (by converting the arraylist to json then store it as string in json format). In the PlayerActivity you retrieve the data, manipulate it then save it. see this post
SQLite
I'm doing a small project as part of the Android course.
The app is supposed to retrieve the most popular movies using some site's API and then display them in a grid view (using posters as thumbnails).
When a specific movie is clicked a new activity will be started providing details.
I've gotten this far:
Obtained a list of most popular movies poster image URL's using the website's API.
Implemented an adapter extending ArrayAdapter which accepts ArrayList as the data source and loads the image from URL into ImageView item.
Populated the GridView using the adapter.
Setup listeners on Gridview using this code:
s:
gridview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
//do stuff according to position/view
}
The info I need to pass onto the movie details activity when a movie is clicked can be obtained in step 1 (I just extracted the poster URLS from the json).
To be able to even provide information when a view is clicked I need to (at the very minimum) also extract the movie's ID and store it.
So as it seems my options are:
Store the IDs in the adapter. Then when a click occurs use getItem(position) to obtain the ID and send it with the intent. The next activity will then have to query the server for the movie details.
This means creating a class:
static class MovieItem {
int Id;
string posterUrl;
}
And converting the adapter to use ArrayList<MovieItem>.
Same as option 1 but instead use setTag to store the Id of the movie.
Same as option 1 but instead obtain all the required information (title, rating, plot, etc..) and store it to MovieItem. Query not required in the next activity.
Same as option 3 but instead use setTag (MovieItem). Query not required in the next activity.
I'm new to app development and I'm trying to get things done the right way, Would appreciate some help.
EDIT:
Also wanted to add, if I had stored additional movie information in the adapter would that not have been appropriate because the information isn't relevant to that class?
Thanks for your troubles! :)
When you first request the list of Movie info, you could store the details for each movie in something like a HashMap<String, HashMap<String, String>>, where the String is the movie id, and the Map is a set of Key/Value pairs for details information. Then, when a click comes through on your onClick, you would use the position to determine which movie poster was clicked. You would then retrieve the details HashMap for the selected movie, put it into a Bundle, pass it to the Intent of your new Activity, then retrieve it on the other side. So the code would look something like this:
When you first retrieve your list of movies, you would do something like this:
//You would put each set of movie data into a HashMap like this...
HashMap<String, HashMap<String, String>> movies = new HashMap<>();
HashMap<String, String> details = new HashMap<>();
details.put("dateReleased", "7.12.2015");
details.put("rating", "PG-13");
details.put("title", "Cool Awesome Movie");
movies.put("12345", details);
//Then when an onClick comes in, you would get the set of
//details for the movie that was selected (based on position)
HashMap<String, String> selectedDetails = movies.get("12345");
//Put the retrieved HashMap of details into a Bundle
Bundle bundle = new Bundle();
bundle.putSerializable("movieDetails", selectedDetails);
//Send the bundle over to the new Activity with the intent
Intent intent = new Intent(this, YourDetailsActivity.class);
intent.putExtras(bundle);
startActivity(intent);
Then when the new Activity starts, you would retrieve the details from the Bundle in your onCreate():
//Then in onCreate() of the new Activity...
Bundle bundle = getIntent().getExtras();
HashMap<String, String> movieDetails = (HashMap<String, String>) bundle.getSerializable("movieDetails");
String dateReleased = movieDetails.get("dateReleased");
String rating = movieDetails.get("rating");
String title = movieDetails.get("title");