My app crashes when the device is in airplane mode - java

I tested my app setting the device in airplane mode, and it crashed.
MoviesListFragment.java
package com.example.android.popularmoviesstage_1;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* A Class that extends Fragment to implement the Movie List structure
* A Fragment represents a behavior or a portion of user interface in a FragmentActivity.
*/
public class MoviesListFragment extends Fragment{
public static ProgressBar mLoadingIndicator;
public static TextView mErrorMessageDisplay;
public static SwipeRefreshLayout mSwipeContainer;
public static PosterAdapter mMoviesAdapter;
private Context mContext;
private MoviesRecyclerView mScrollListener;
private int mPage;
private int mSorting;
private static final int SORTING_POPULAR = 1;
private static final int SORTING_RATED = 2;
private static final String BUNDLE_MOVIES_KEY = "movieList";
private static final String BUNDLE_PAGE_KEY = "currentPage";
private static final String BUNDLE_SORTING_KEY = "currentSorting";
private static final String BUNDLE_ERROR_KEY = "errorShown";
private static final String TAG = MoviesListFragment.class.getSimpleName();
#Override
public void onCreate(#Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setHasOptionsMenu(true); //Allowing menu options in the ActionBar
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState){
Boolean errorShown = false;
if (savedInstanceState != null){
errorShown = savedInstanceState.getBoolean(BUNDLE_ERROR_KEY);
}
if (savedInstanceState != null && !errorShown){
mPage = savedInstanceState.getInt(BUNDLE_PAGE_KEY);
mSorting = savedInstanceState.getInt(BUNDLE_SORTING_KEY);
} else {
mPage = 1;
mSorting = 1;
}
//inflating the movies in this fragment
View rootView = inflater.inflate(R.layout.movie_list_fragment, container, false);
mContext = getContext();
final int columns = getResources().getInteger(R.integer.grid_columns);
// Laying the movie items in grid formation.
GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, columns, GridLayoutManager.VERTICAL, false);
RecyclerView recyclerView = rootView.findViewById(R.id.rv_posters);
recyclerView.setLayoutManager(gridLayoutManager);
//setting the size for all movie items
recyclerView.setHasFixedSize(true);
mMoviesAdapter = new PosterAdapter();
recyclerView.setAdapter(mMoviesAdapter);
//progress indicator catching movie data from the internet
mLoadingIndicator = rootView.findViewById(R.id.pb_loading_indicator);
mScrollListener = new MoviesRecyclerView(gridLayoutManager, mPage) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
Log.d(TAG, "Loading page: " + String.valueOf(page));
mPage = page;
loadCards(mSorting);
}
};
recyclerView.addOnScrollListener(mScrollListener);
//The SwipeRefreshLayout is used whenever the user refresh the contents of a view via a vertical swipe gesture.
mSwipeContainer = rootView.findViewById(R.id.sr_swipe_container);
mSwipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mErrorMessageDisplay.setVisibility(View.INVISIBLE);
clearGridView();
loadCards(mSorting);
}
});
mSwipeContainer.setColorSchemeResources(R.color.colorAccent);
mErrorMessageDisplay = rootView.findViewById(R.id.tv_error_message_display);
if (savedInstanceState != null && !errorShown){
ArrayList<Movie> movieArrayList = savedInstanceState.getParcelableArrayList(BUNDLE_MOVIES_KEY);
mMoviesAdapter.setMoviesData(movieArrayList);
} else {
loadCards(mSorting);
}
return rootView;
}
//onSaveInstanceState() is called before your activity is paused.
// So any info that it needs after it is potentially destroyed can be retrieved from the saved Bundle
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
List<Movie> movieList = mMoviesAdapter.getMoviesData();
if (movieList != null){
ArrayList<Movie> movieArrayList = new ArrayList<>(mMoviesAdapter.getMoviesData());
outState.putParcelableArrayList(BUNDLE_MOVIES_KEY, movieArrayList);
outState.putInt(BUNDLE_PAGE_KEY, mPage);
outState.putInt(BUNDLE_SORTING_KEY, mSorting);
} else {
if (mErrorMessageDisplay.isShown()){
outState.putBoolean(BUNDLE_ERROR_KEY, true);
}
}
}
/**
* A method that invokes the AsyncTask to populate the RecyclerView,
* it's based on the sorting option selected by the user. Default is "popular movies"
*
* #param sorting the way of sorting selected by the user
*/
private void loadCards(int sorting){
if(NetworkUtils.isOnline(mContext)){
String method;
switch (sorting){
case SORTING_POPULAR:
method = NetworkUtils.getMoviesPopular();
break;
case SORTING_RATED:
method = NetworkUtils.getMoviesTopRated();
break;
default:
method = NetworkUtils.getMoviesPopular();
break;
}
String[] posters = new String[]{method, String.valueOf(mPage)};
new FetchMovieTask().execute(posters);
} else {
showErrorMessage(R.string.error_no_connectivity);
if (mSwipeContainer.isRefreshing()) {
mSwipeContainer.setRefreshing(false);
}
}
}
/**
* Reset the GridView properties and adapter
*/
private void clearGridView(){
mScrollListener.resetState();
mPage = 1;
mMoviesAdapter.clear();
}
/**
* Display the specific error message in the TextView
*
* #param messageId the resource id of the error string
*/
public static void showErrorMessage(int messageId){
mErrorMessageDisplay.setText(Resources.getSystem().getText(messageId));
mErrorMessageDisplay.setVisibility(View.VISIBLE);
}
//onCreateOptionsMenu() to specify the options menu for an activity.
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu);
switch (mSorting) {
case SORTING_POPULAR:
menu.findItem(R.id.sort_popular).setChecked(true);
break;
case SORTING_RATED:
menu.findItem(R.id.sort_rated).setChecked(true);
break;
default:
menu.findItem(R.id.sort_popular).setChecked(true);
break;
}
}
/*
*When the user selects an item from the options menu (including action items in the app bar),
* the system calls our activity's onOptionsItemSelected() method.
* This method passes the MenuItem selected. We can identify the item by calling getItemId(),
* which returns the unique ID for the menu item (defined by the android:id attribute in the menu
* resource or with an integer given to the add() method). We can match this ID against known menu
* items to perform the appropriate action.
*/
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.sort_popular || id == R.id.sort_rated) {
if (!item.isChecked()) {
mSorting = item.getOrder();
item.setChecked(true);
clearGridView();
loadCards(mSorting);
}
return true;
}
return super.onOptionsItemSelected(item);
}
}
MainActivity.java
package com.example.android.popularmoviesstage_1;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
/**
* MainActivity, which is presented to the user when the app is launched.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//MoviesListFragment is used here
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.popularmoviesstage_1.MainActivity">
<!-- This fragment is used in MainActivity -->
<fragment
android:name="com.example.android.popularmoviesstage_1.MoviesListFragment"
android:id="#+id/f_movie_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="#layout/movie_list_fragment" />
</FrameLayout>
movie_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/padding_ten"
android:paddingRight="#dimen/padding_ten">
<TextView
android:id="#+id/tv_error_message_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="#dimen/padding_sixteen"
android:text="#string/error_message"
android:drawableRight="#android:drawable/stat_notify_error"
android:drawableEnd="#android:drawable/stat_notify_error"
android:drawableTint="#android:color/holo_red_dark"
android:drawablePadding="#dimen/padding_four"
android:textSize="#dimen/movie_detail_text_size"
android:background="#android:color/white"
android:visibility="invisible" />
<ProgressBar
android:id="#+id/pb_loading_indicator"
android:layout_width="#dimen/padding_fourty_two"
android:layout_height="#dimen/padding_fourty_two"
android:layout_gravity="center"
android:indeterminateTint="#android:color/holo_blue_bright"
android:visibility="invisible" />
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/sr_swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_posters"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
strings.xml
<resources>
<string name="app_name">Popular Movies</string>
<string name="sort_popular">Most Popular</string>
<string name="sort_rated">Top Rated</string>
<string name="poster_image_alt">Poster Image</string>
<string name="error_message">An error has occurred. Please try again by swiping down</string>
<string name="error_no_connectivity">Your device is not connected to the Internet.</string>
<string name="error_moviedb_list">Error from the MovieDB Service. Swipe down to try again</string>
<string name="error_movie_poster">Error loading the poster, sorry!</string>
<string name="no_internet_connection">No Internet connection</string>
<string name="button_retry">RETRY</string>
<string name="checkInternetConnection">Check Internet</string>
</resources>
This is the error description:
FATAL EXCEPTION: main
Process: com.example.android.popularmoviesstage_1, PID: 21294
android.content.res.Resources$NotFoundException: String resource ID #0x7f0d0026
Here is the stacktrace of this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android.popularmoviesstage_1, PID: 21294
android.content.res.Resources$NotFoundException: String resource ID #0x7f0d0026
at android.content.res.Resources.getText(Resources.java:331)
at com.example.android.popularmoviesstage_1.MoviesListFragment.showErrorMessage(MoviesListFragment.java:185)
at com.example.android.popularmoviesstage_1.MoviesListFragment.loadCards(MoviesListFragment.java:161)
at com.example.android.popularmoviesstage_1.MoviesListFragment.access$300(MoviesListFragment.java:28)
at com.example.android.popularmoviesstage_1.MoviesListFragment$2.onRefresh(MoviesListFragment.java:104)
at android.support.v4.widget.SwipeRefreshLayout$1.onAnimationEnd(SwipeRefreshLayout.java:188)
at android.support.v4.widget.CircleImageView.onAnimationEnd(CircleImageView.java:106)
at android.view.ViewGroup.finishAnimatingView(ViewGroup.java:6278)
at android.view.View.draw(View.java:17027)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.draw(View.java:17086)
at android.view.View.updateDisplayListIfDirty(View.java:16065)
at android.view.View.draw(View.java:16849)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.updateDisplayListIfDirty(View.java:16060)
at android.view.View.draw(View.java:16849)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.updateDisplayListIfDirty(View.java:16060)
at android.view.View.draw(View.java:16849)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.updateDisplayListIfDirty(View.java:16060)
at android.view.View.draw(View.java:16849)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.updateDisplayListIfDirty(View.java:16060)
at android.view.View.draw(View.java:16849)
at android.view.ViewGroup.drawChild(ViewGroup.java:3768)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3554)
at android.view.View.draw(View.java:17086)
at com.android.internal.policy.DecorView.draw(DecorView.java:751)
at android.view.View.updateDisplayListIfDirty(View.java:16065)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:657)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:663)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:771)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2808)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2616)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2223)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1258)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6348)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:871)
at android.view.Choreographer.doCallbacks(Choreographer.java:683)
at android.view.Choreographer.doFrame(Choreographer.java:619)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:857)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)

In order to get a String in a fragment use the following code
getResources().getString(R.id.error_no_connectivity).
This will fix the crash for you. You are not referencing the string in your strings.xml correctly
You method should be (Removed the static keyword)
public void showErrorMessage(int messageId){
mErrorMessageDisplay.setText(getResources().getString(messageId));
mErrorMessageDisplay.setVisibility(View.VISIBLE);
}

Related

How to keep scrolling position in ScrollView after rotating the screen in fragment with LiveDate object loaded

I have a fragment which is to display a lyrics of a song which is loaded from room databse based on its given id.
I'd like to preserve scrolling position after rotating the screen. Now after rotating the song is loaded again from db and the view is on the very top regardless of the scrolling position befor the rotating.
I thought that I can save scrolling position in onSaveInstanceState some bundle in onCreateView() use command on mSongDisplayScrollView.scrollTo(x, y)
The fragment code:
public class SongDisplayFragment extends Fragment {
private Song mSongToDisplay;
private ScrollView mSongDisplayScrollView;
private TextView mSongTitleTextView;
private RecyclerView mSongLyricsRecyclerView;
private GuitarSongbookViewModel mGuitarSongbookViewModel;
public static final String SONG_ID_KEY = "SONG_ID_KEY";
public SongDisplayFragment() {
}
public static SongDisplayFragment newInstance(Long songId) {
SongDisplayFragment songDisplayFragment = new SongDisplayFragment();
Bundle arguments = new Bundle();
arguments.putLong(SONG_ID_KEY,
songDisplayFragment.setArguments(arguments);
return songDisplayFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_song_display, container, false);
mSongDisplayScrollView = view.findViewById(R.id.song_display_scroll_view);
mSongLyricsRecyclerView = view.findViewById(R.id.lyrics_rv_);
mSongTitleTextView = view.findViewById(R.id.
mGuitarSongbookViewModel = ViewModelProviders.of(this).get(GuitarSongbookViewModel.class);
final SongDisplayAdapter songDisplayAdapter = new SongDisplayAdapter(getContext());
Long songId = null;
if (getArguments().containsKey(SONG_ID_KEY)) {
songId = getArguments().getLong(SONG_ID_KEY);
}
if (songId != null) {
final Long finalSongId = songId;
mGuitarSongbookViewModel.getSongById(songId).observe(this, new Observer<Song>() {
#Override
public void onChanged(#Nullable final Song song) {
mSongToDisplay = song;
mSongTitleTextView.setText(mSongToDisplay.getMTitle());
songDisplayAdapter.setSong(song);
}
});
}
mSongLyricsRecyclerView.setAdapter(songDisplayAdapter);
mSongLyricsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
}
The fragment XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SongDisplayFragment">
<ScrollView
android:id="#+id/song_display_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/autoscroll_bar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
tools:layout_editor_absoluteX="138dp">
<LinearLayout
android:id="#+id/son_display_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/displayed_song_title_txt_"
style="#style/TitleOfDisplayedSong"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="#string/title_placeholder" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/lyrics_rv_"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
The fragment displays a tiltle and lyrics of a song in a textView and RecyclerView which adapter class code is:
package com.example.guitarsongbook.adapters;
import android.content.Context;
import android.graphics.Rect;
import android.text.Html;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.guitarsongbook.R;
import com.example.guitarsongbook.daos.SongChordJoinDao;
import com.example.guitarsongbook.model.Chord;
import com.example.guitarsongbook.model.Song;
import java.util.ArrayList;
import java.util.List;
public class SongDisplayAdapter extends RecyclerView.Adapter<SongDisplayAdapter.SongViewHolder> {
private Context context;
private final LayoutInflater mInflater;
private Song mSong;
private ArrayList<String> mLyrics;
public SongDisplayAdapter(Context context) {
this.context = context;
mInflater = LayoutInflater.from(context);
}
#NonNull
#Override
public SongViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = mInflater.inflate(R.layout.song_lyrics_rv_item, parent, false);
return new SongDisplayAdapter.SongViewHolder(itemView);
}
public void setSong(Song song){
mSong = song;
mLyrics = mSong.getMLyrics();
notifyDataSetChanged();
}
#Override
public void onBindViewHolder(#NonNull SongViewHolder holder, int position) {
holder.bindTo(position);
}
#Override
public int getItemCount() {
if (mLyrics != null)
return mLyrics.size();
else return 0;
}
public class SongViewHolder extends RecyclerView.ViewHolder {
private final TextView mLyricsLineTextView;
public SongViewHolder(#NonNull View itemView) {
super(itemView);
mLyricsLineTextView = itemView.findViewById(R.id.song_lyric_line_txt_);
}
public void bindTo(int position) {
if (mSong != null) {
mLyricsLineTextView.setText(Html.fromHtml(mLyrics.get(position)));
} else {
mLyricsLineTextView.setText(context.getString(R.string.no_song_label));
}
}
}
}
RecyclerView item XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/song_lyric_line_txt_"
style="#style/LyricOfDisplayedSong"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toStartOf="#+id/song_chord_line_txt_"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="placeholder text" />
</androidx.constraintlayout.widget.ConstraintLayout>
I think that the problem is that after rotating the screen song has to be loaded again by observer's methon onChange() so there is a short moment that there is nothing to display for scrollView. I'd like to find some solution how to return to the old position after again loading the song.
The first step is to persist the values of the objects through your Fragment's onSaveInstanceState method.
More of my explanations would come from the comments within each code block, so you can understand better on how it all works.
Values to be persisted: mSongToDisplay and mScrollViewPosition
//new variable introduced
int mScrollViewPosition = 0;
#Override
protected void onSaveInstanceState(Bundle outState) {
//If screen orientation changes, android redraws all views
//and non persistent data would be lost.
//Nevertheless, before this happens android informs the app via this method
//So to prevent state loss we save the data to a temporary storage
outState.putParcelable("song_data", mSongToDisplay);
//save ScrollView current position
outState.putInt("position_data", mSongDisplayScrollView.getScrollY());
//call super to commit your changes
super.onSaveInstanceState(outState);
}
Next is to recover the data after orientation change is completed
Add the following block in your fragments onCreateView and make sure the if statement block comes after initializing your adapter
...
//...add adapter initialization block here before checking for saved data
//Check for saved data
if (savedInstanceState != null) {
// Retrieve the data you saved
mSongToDisplay = savedInstanceState.getParcelable("song_data");
mScrollViewPosition = savedInstanceState.getInt("position_data");
//Re-initialize and reload adapter record
mSongToDisplay = song;
mSongTitleTextView.setText(mSongToDisplay.getMTitle());
songDisplayAdapter.setSong(song);
}
else {
//No data to retrieve
//initialize data model here.
}
//Assign the adapter to recyclerView
mSongLyricsRecyclerView.setAdapter(songDisplayAdapter);
mSongLyricsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try{
//Finally we set the scrollview position at this point just to make sure
//recyclerview has its data loaded and full length of scrollview is restored
mSongDisplayScrollView.setScrollY(mScrollViewPosition);}
catch(Exception ex){
//position isn't right
}
return view;
At this point the required data has been persisted and on every screenOrientationChange the app would load the previously saved song object into the adapter preventing a false call to the db or api, and would also scroll to the expected position using the saved integer value mScrollViewPosition.

My app stopped every time when I load it

EDIT Happy New Year! To be precise ,I created a simple music app in Android Studio who read mp3 files from my internal storage. All good until I decide to put one random image on every single track on the list. (In front of the name of the song I want to appear one image).When I select one single image in 'src' at ImageView ,it works and appear to all songs. I will atach a part of my code below: Main Activity:
package com.alg.amzar.spectrum;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
private ArrayList<Song> songList;
private ListView songView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
schimbare();
songView = (ListView)findViewById(R.id.song_list);
songList = new ArrayList<Song>();
getSongList();
Collections.sort(songList, new Comparator<Song>(){
public int compare(Song a, Song b){
return a.getTitle().compareTo(b.getTitle());
}
});
SongAdapter songAdt = new SongAdapter(this, songList);
songView.setAdapter(songAdt);
}
public void schimbare() {
int[] photos={R.drawable.img_0, R.drawable.img_1,R.drawable.img_2};
ImageView image = (ImageView) findViewById(R.id.imagine);
Random ran=new Random();
int i=ran.nextInt(photos.length);
image.setImageResource(photos[i]);
}
public void getSongList() {
ContentResolver musicResolver = getContentResolver();
Uri musicUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicUri, null, null, null, null);
if(musicCursor!=null && musicCursor.moveToFirst()){
//get columns
int titleColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = musicCursor.getColumnIndex
(android.provider.MediaStore.Audio.Media._ID);
//add songs to list
do {
long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
songList.add(new Song(thisId, thisTitle));
}
while (musicCursor.moveToNext());
}
}
public native String stringFromJNI();
}
Activity Main .xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#FF330000"
tools:context=".MainActivity" >
<ListView
android:id="#+id/song_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Song.xml :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="songPicked"
android:orientation="vertical"
android:padding="5dp" >
<ImageView
android:layout_width="70dp"
android:id="#+id/imagine"
android:layout_height="50dp" />
<TextView
android:id="#+id/song_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#FFFFFF99"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginBottom="20dp"
android:layout_marginLeft="75dp"
android:layout_marginTop="-42dp"/>
</LinearLayout>
Beside this ,I've 2 more java classes. I think the problem is here ,but idk what is wrong:
public void schimbare() {
int[] photos={R.drawable.img_0, R.drawable.img_1,R.drawable.img_2};
ImageView image = (ImageView) findViewById(R.id.imagine);
Random ran=new Random();
int i=ran.nextInt(photos.length);
image.setImageResource(photos[i]);
}
Another wondering of mine is what 'deprecated' means when is I use .getDrawable.
Thanks in advance!
Logcat:
ime: FATAL EXCEPTION: main
Process: com.alg.amzar.spectrum, PID: 28677
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alg.amzar.spectrum/com.alg.amzar.spectrum.MainActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2381)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2433)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1346)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:5341)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:830)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:646)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.alg.amzar.spectrum.MainActivity.schimbare(MainActivity.java:57)
at com.alg.amzar.spectrum.MainActivity.onCreate(MainActivity.java:31)
at android.app.Activity.performCreate(Activity.java:5343)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1088)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2335)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2433) 
at android.app.ActivityThread.access$800(ActivityThread.java:155) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1346) 
at android.os.Handler.dispatchMessage(Handler.java:110) 
at android.os.Looper.loop(Looper.java:193) 
at android.app.ActivityThread.main(ActivityThread.java:5341) 
at java.lang.reflect.Method.invokeNative(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:515) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:830) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:646) 
at dalvik.system.NativeStart.main(Native Method) 
My phone android version: 4.4.2 (API 19)
Target SDK Version:"19"
MinSDK Version:"14"
SongAdapter.java:
package com.alg.amzar.spectrum;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class SongAdapter extends BaseAdapter {
private ArrayList<Song> songs;
private LayoutInflater songInf;
public SongAdapter(Context c, ArrayList<Song> theSongs) {
songs = theSongs;
songInf = LayoutInflater.from(c);
}
#Override
public int getCount() {
return songs.size();
}
#Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//map to song layout
LinearLayout songLay = (LinearLayout)songInf.inflate
(R.layout.song, parent, false);
//get title and artist views
TextView songView = (TextView)songLay.findViewById(R.id.song_title);
//get song using position
Song currSong = songs.get(position);
//get title and artist strings
songView.setText(currSong.getTitle());
//set position as tag
songLay.setTag(position);
return songLay;
}
}
In your case, findViewById(R.id.imagine) returns NULL because it doesn't exist in the layout 'activity_main', which you provided in setContentView(). findViewById() returns NULL if the view doesn't exist in current layout. R.id.imagine exists in the layout song.xml.
I think, you are trying to inflate the ListView with layout 'song'. Only after inflating you can call findViewById and setImageResource. You can done it in class SongAdapter, for each elements in ListView after inflating them.
Another suggestion, instead of using random images you can get album art of a song from MediaMetadataRetriever class. You can refer android documentation for that. But you have to solve this exception before.
Comment/ Delete function schimbare() in onCreate and edit SongAdapter like following.
package com.alg.amzar.spectrum;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class SongAdapter extends BaseAdapter {
private ArrayList<Song> songs;
private LayoutInflater songInf;
int[] photos={R.drawable.img_0, R.drawable.img_1,R.drawable.img_2};
Random ran=new Random();
// CHECK THIS:
public SongAdapter(Context c, ArrayList<Song> theSongs) {
songs = theSongs;
// CHECK THIS:
songInf = ( LayoutInflater )c.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return songs.size();
}
#Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//map to song layout
LinearLayout songLay = (LinearLayout)songInf.inflate
(R.layout.song, null);
//get title and artist views
TextView songView = (TextView)songLay.findViewById(R.id.song_title);
// CHECK THIS:
ImageView img = (ImageView)songLay.findViewById(R.id.imagine);
//get song using position
Song currSong = songs.get(position);
//get title and artist strings
songView.setText(currSong.getTitle());
// CHECK THIS:
int i=ran.nextInt(photos.length);
img.setImageResource(photos[i]);
//set position as tag
songLay.setTag(position);
return songLay;
}
}
Deprecated mean that google no longer support this method, they telling you there is NEWER method you should use. the deprecated method is working fine FOR NOW. But some day (maybe tomorrow maybe 3 years from today) it will no longer work.
about the error, upload the log please.
The error is at line 57:
at com.alg.amzar.spectrum.MainActivity.schimbare(MainActivity.java:57)
I am not sure where line 57 is but you can check it in your code, if you still cant find the cause then update us which line is 57
public void schimbare() {
int[] photos={R.drawable.img_0, R.drawable.img_1,R.drawable.img_2};
ImageView image = (ImageView) findViewById(R.id.imagine);
Random ran=new Random();
// CHANGE photos.length to photos.length-1
int i=ran.nextInt(photos.length);
//ADD this 2 line of code below, then you can check in your Log if the problem is with the view (the view is null), or if the photos position is null.
Log.d("bug_tag",String.valueOf(photos[i]));
Log.d("bug_tag",String.valueOf(image.getId()));
image.setImageResource(photos[i]);
}

Android nullpointer Screenslider (view pager)

I have downloaded the demo screen slider from android (https://developer.android.com/training/animation/screen-slide.html).
I've tried to put this in my own project but I get this error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.pws.test.demo/com.pws.test.demo.E_D_Uitgebreid}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2331)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2389)
at android.app.ActivityThread.access$900(ActivityThread.java:169)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1277)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5479)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.pws.test.demo.E_D_Uitgebreid.onCreate(E_D_Uitgebreid.java:51)
at android.app.Activity.performCreate(Activity.java:5451)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1093)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2295)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2389)
at android.app.ActivityThread.access$900(ActivityThread.java:169)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1277)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5479)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(Native Method)
This is my code:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.NavUtils;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuItem;
/**
* Demonstrates a "screen-slide" animation using a {#link ViewPager}. Because {#link ViewPager}
* automatically plays such an animation when calling {#link ViewPager#setCurrentItem(int)}, there
* isn't any animation-specific code in this sample.
*
* <p>This sample shows a "next" button that advances the user to the next step in a wizard,
* animating the current screen out (to the left) and the next screen in (from the right). The
* reverse animation is played when the user presses the "previous" button.</p>
*
* #see
*/
public class E_D_Uitgebreid extends FragmentActivity {
/**
* The number of pages (wizard steps) to show in this demo.
*/
private static final int NUM_PAGES = 3;
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private ViewPager mPager;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_e__d__uitgebreid__fragment);
// Instantiate a ViewPager and a PagerAdapter.
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When changing pages, reset the action bar actions since they are dependent
// on which page is currently active. An alternative approach is to have each
// fragment expose actions itself (rather than the activity exposing actions),
// but for simplicity, the activity provides the actions in this sample.
invalidateOptionsMenu();
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_e__d__uitgebreid, menu);
menu.findItem(R.id.action_previous).setEnabled(mPager.getCurrentItem() > 0);
// Add either a "next" or "finish" button to the action bar, depending on which page
// is currently selected.
MenuItem item = menu.add(Menu.NONE, R.id.action_next, Menu.NONE,
(mPager.getCurrentItem() == mPagerAdapter.getCount() - 1)
? R.string.action_finish
: R.string.action_next);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// Navigate "up" the demo structure to the launchpad activity.
// See http://developer.android.com/design/patterns/navigation.html for more.
NavUtils.navigateUpTo(this, new Intent(this, D_D_RondleidingMenu.class));
return true;
case R.id.action_previous:
// Go to the previous step in the wizard. If there is no previous step,
// setCurrentItem will do nothing.
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
return true;
case R.id.action_next:
// Advance to the next step in the wizard. If there is no next step, setCurrentItem
// will do nothing.
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
return true;
}
return super.onOptionsItemSelected(item);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return E_D_Uitgebreid_Fragment.create(position);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
}
And the fragment:
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
/**
* A fragment representing a single step in a wizard. The fragment shows a dummy title indicating
* the page number, along with some dummy text.
*
* <p>This class is used by the and {#link
* } samples.</p>
*/
public class E_D_Uitgebreid_Fragment extends Fragment {
/**
* The argument key for the page number this fragment represents.
*/
public static final String ARG_PAGE = "page";
/**
* The fragment's page number, which is set to the argument value for {#link #ARG_PAGE}.
*/
private int mPageNumber;
/**
* Factory method for this fragment class. Constructs a new fragment for the given page number.
*/
public static E_D_Uitgebreid_Fragment create(int pageNumber) {
E_D_Uitgebreid_Fragment fragment = new E_D_Uitgebreid_Fragment();
Bundle args = new Bundle();
args.putInt(ARG_PAGE, pageNumber);
fragment.setArguments(args);
return fragment;
}
public E_D_Uitgebreid_Fragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPageNumber = getArguments().getInt(ARG_PAGE);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout containing a title and body text.
ViewGroup rootView = (ViewGroup) inflater
.inflate(R.layout.activity_e__d__uitgebreid__fragment, container, false);
// Set the title view to show the page number.
((TextView) rootView.findViewById(android.R.id.text1)).setText(
Articles.paginatitel[mPageNumber]);
((TextView) rootView.findViewById(android.R.id.text2)).setText(
Articles.paginainhoud[mPageNumber]);
((ImageView) rootView.findViewById(android.R.id.background)).setImageResource(Articles.afbeelding[mPageNumber]);
return rootView;
}
/**
* Returns the page number represented by this fragment object.
*/
public int getPageNumber() {
return mPageNumber;
}
}
And also the menu activity:
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
public class D_D_RondleidingMenu extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_d__d__rondleiding_menu);
SetupUitgebreideRondleidingButton();
}
private void SetupUitgebreideRondleidingButton() {
Button MessageButton = (Button) findViewById(R.id.settingsbutton);
MessageButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(D_D_RondleidingMenu.this, E_D_Uitgebreid.class));
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_d__d__rondleiding_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
activity_e_d_uitgebreid_fragment.xml:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Dummy content. -->
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<ImageView android:id="#android:id/background"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_gravity="center_horizontal"
/>
<TextView android:id="#android:id/text1"
style="?android:textAppearanceLarge"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<TextView android:id="#android:id/text2"
style="?android:textAppearanceMedium"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/lorem_ipsum"
/>
</LinearLayout>
</ScrollView>
I've already changed this, but this didn't work:
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
and this:
#Override
public android.support.v4.app.Fragment getItem(int position) {
return E_D_Uitgebreid_Fragment.create(position);
}
ensure that the button with ID R.id.settingsbutton is exist in layout R.layout.activity_d__d__rondleiding_menu if exist please set activity_d__d__rondleiding_menu XML

How can I add a large profile picture to my navigation drawer?

So, a couple of days ago I started working on my very first app. It's an app with a navigation drawer where users can set some website shortcuts to be displayed in the navigation drawer. If a user clicks on a website it opens in a webview. Really nothing fancy, but it's a concept I came up with to train my Android knowledge.
I finished the basics of this app this morning with some help, but now I want to add a large profile picture to the navigation drawer. (You can see it here on the left: http://www.droid-life.com/wp-content/uploads/2013/12/new-foursquare.jpg)
I understand that I should add an <ImageView> to the navigation drawer, but every time I try that the log keeps saying I have junk in my XML...
Below you can find the code I already wrote. I hope you guys can help me...
Main.java
package test.webviewapp;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class Main
extends ActionBarActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks
{
/**
* Fragment managing the behaviors, interactions and presentation of the
* navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in
* {#link #restoreActionBar()}.
*/
private CharSequence mTitle;
// we need a class level references to some objects to be able to modify the
// target address outside of onCreate()
private WebView myWebView;
private ActionBar actionBar;
private ProgressDialog progressDialog;
// keep the pair of String arrays of site names and addresses
private String[] siteNames;
private String[] siteAddresses;
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// grab the needed website arrays
siteNames = getResources().getStringArray(R.array.site_names);
siteAddresses = getResources().getStringArray(R.array.site_addresses);
// set up WebView. initial page load comes from NavDrawerFragment attach
myWebView = (WebView) findViewById(R.id.main_webview);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.setWebChromeClient(new WebChromeClient());
myWebView.setWebViewClient(new WebViewClient()
{
#Override
public void onPageFinished(WebView view, String url)
{
// when a page has finished loading dismiss any progress dialog
if (progressDialog != null && progressDialog.isShowing())
{
progressDialog.dismiss();
}
String javascript="javascript:"+
"document.getElementById('menu-toggle').css('display','none');";
myWebView.evaluateJavascript(javascript, null);
}
});
// Set up the drawer.
mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
.findFragmentById(R.id.navigation_drawer);
mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
public void onNavigationDrawerItemSelected(int siteIndex)
{
// user selected page load
Log.d(TAG, "(onNavSelect) received index: " + siteIndex);
loadWebPage(siteIndex);
}
public void onSectionAttached(int siteIndex)
{
// initial page load. not user selected.
loadWebPage(siteIndex);
}
public void restoreActionBar()
{
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
private void loadWebPage(int siteIndex)
{
// lets show a progress indicator instead of a blank screen
if (progressDialog == null)
{
initProgressDialog();
}
progressDialog.show();
// load the page
Log.d(TAG, "(loadWebPage) Loading page: " + siteNames[siteIndex] + "("
+ siteAddresses[siteIndex] + ")");
mTitle = siteNames[siteIndex];
if (actionBar == null)
{
restoreActionBar();
}
else
{
actionBar.setTitle(mTitle);
}
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.loadUrl(siteAddresses[siteIndex]);
myWebView.loadUrl(siteAddresses[siteIndex]);
// progressDialog gets dismissed above in WebViewclient declaration
}
private void initProgressDialog()
{
progressDialog = new ProgressDialog(this, ProgressDialog.STYLE_SPINNER);
progressDialog.setIndeterminate(true);
progressDialog.setMessage(getString(R.string.page_load_progress_message));
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
if (!mNavigationDrawerFragment.isDrawerOpen())
{
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.global, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
if (actionBar == null)
{
restoreActionBar();
}
else
{
}
int id = item.getItemId();
if (id == R.id.action_settings)
{
myWebView.loadUrl(settingsview);
myWebView.loadUrl(settingsview);
actionBar.setTitle("Settings");
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment
extends Fragment
{
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SELECTED_SITE_INDEX = "selected_site_index";
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int siteIndex)
{
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SELECTED_SITE_INDEX, siteIndex);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment()
{}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
// Here is where you can define the default page you want loaded
// or if you want to save/restore the last page viewed etc.
((Main) activity)
.onSectionAttached(getArguments().getInt(ARG_SELECTED_SITE_INDEX));
}
}
}
NavigationDrawerFragment.java
package test.webviewapp;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/**
* Fragment used for managing interactions for and presentation of a
* navigation drawer. See the <a href=
* "https://developer.android.com/design/patterns/navigation-drawer.html#Interaction"
* > design guidelines</a> for a complete explanation of the behaviors
* implemented here.
*/
public class NavigationDrawerFragment
extends Fragment
implements AdapterView.OnItemClickListener
{
private String[] siteNames;
private static final String TAG = "NavDrawerFrag";
/**
* Remember the position of the selected item.
*/
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
/**
* Per the design guidelines, you should show the drawer on launch until
* the user manually expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
/**
* A pointer to the current callbacks instance (the Activity).
*/
private NavigationDrawerCallbacks mCallbacks;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawerLayout;
private ListView mDrawerListView;
private View mFragmentContainerView;
private int mCurrentSelectedPosition = 0;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
public NavigationDrawerFragment()
{}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null)
{
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
// Select either the default item (0) or the last selected item.
// too early to select an item
//selectItem(mCurrentSelectedPosition);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
// Indicate that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true);
selectItem(mCurrentSelectedPosition);
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
siteNames = getActivity().getResources().getStringArray(R.array.site_names);
Log.d(TAG, "number of sites loaded: " + siteNames.length);
mDrawerListView = (ListView) inflater.inflate(R.layout.fragment_navigation_drawer,
container,
false);
// neater to implement OnItemClickListener and define onClick() later
mDrawerListView.setOnItemClickListener(this);
String[] siteNames = getActivity().getResources().getStringArray(R.array.site_names);
mDrawerListView.setAdapter(new ArrayAdapter<String>(getActionBar().getThemedContext(),
android.R.layout.simple_list_item_1, android.R.id.text1, siteNames));
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return mDrawerListView;
}
public boolean isDrawerOpen()
{
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
}
/**
* Users of this fragment must call this method to set up the navigation
* drawer interactions.
*
* #param fragmentId
* The android:id of this fragment in its activity's layout.
* #param drawerLayout
* The DrawerLayout containing this fragment's UI.
*/
public void setUp(int fragmentId, DrawerLayout drawerLayout)
{
mFragmentContainerView = getActivity().findViewById(fragmentId);
mDrawerLayout = drawerLayout;
// set a custom shadow that overlays the main content when the drawer opens
//mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
// set up the drawer's list view with items and click listener
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
mDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout,
R.drawable.ic_drawer, R.string.navigation_drawer_open,
R.string.navigation_drawer_close)
{
#Override
public void onDrawerClosed(View drawerView)
{
super.onDrawerClosed(drawerView);
if (!isAdded())
{
return;
}
getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
#Override
public void onDrawerOpened(View drawerView)
{
super.onDrawerOpened(drawerView);
if (!isAdded())
{
return;
}
if (!mUserLearnedDrawer)
{
// The user manually opened the drawer; store this flag to prevent auto-showing
// the navigation drawer automatically in the future.
mUserLearnedDrawer = true;
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(getActivity());
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
}
getActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
};
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!mUserLearnedDrawer && !mFromSavedInstanceState)
{
mDrawerLayout.openDrawer(mFragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable()
{
#Override
public void run()
{
mDrawerToggle.syncState();
}
});
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
private void selectItem(int siteIndex)
{
mCurrentSelectedPosition = siteIndex;
if (mDrawerListView != null)
{
Log.d(TAG, "(select) list ok");
mDrawerListView.setItemChecked(siteIndex, true);
}
if (mDrawerLayout != null)
{
Log.d(TAG, "(select) drawer ok");
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null)
{
Log.d(TAG, "(select) callback...");
mCallbacks.onNavigationDrawerItemSelected(siteIndex);
}
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
mCallbacks = (NavigationDrawerCallbacks) activity;
}
catch (ClassCastException e)
{
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
#Override
public void onDetach()
{
super.onDetach();
mCallbacks = null;
}
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mDrawerToggle.onConfigurationChanged(newConfig);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
// If the drawer is open, show the global app actions in the action bar. See also
// showGlobalContextActionBar, which controls the top-left area of the action bar.
if (mDrawerLayout != null && isDrawerOpen())
{
inflater.inflate(R.menu.global, menu);
showGlobalContextActionBar();
}
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (mDrawerToggle.onOptionsItemSelected(item))
{
//fragment = new PlanetFragment();
}
return super.onOptionsItemSelected(item);
}
/**
* Per the navigation drawer design guidelines, updates the action bar to
* show the global app 'context', rather than just what's in the current
* screen.
*/
private void showGlobalContextActionBar()
{
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setTitle(R.string.app_name);
}
private ActionBar getActionBar()
{
return ((ActionBarActivity) getActivity()).getSupportActionBar();
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Log.i(TAG, "(Click) index: " + position);
Log.i(TAG, "(Click) site: " + siteNames[position]);
selectItem(position);
}
/**
* Callbacks interface that all activities using this fragment must
* implement.
*/
public static interface NavigationDrawerCallbacks
{
/**
* Called when an item in the navigation drawer is selected.
*/
void onNavigationDrawerItemSelected(int position);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<fragment
android:id="#+id/navigation_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:name="test.webdrawerapp.NavigationDrawerFragment"
tools:layout="#layout/fragment_navigation_drawer"/>
</android.support.v4.widget.DrawerLayout>
fragment_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".main$PlaceholderFragment">
</RelativeLayout>
fragment_navigation_drawer.xml
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#000"
tools:context=".NavigationDrawerFragment" />
From working with you on this app before I was able to make some tweaks for you.
What had to be done was to add new Views to fragment_navigation_drawer.xml and change the NavigationDrawerFragment to match up and include the changes.
Layout
<?xml version="1.0" encoding="utf-8"?>
<!-- the root view is now a LinearLayout, all other Views are children of this -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#cccc"
android:orientation="vertical">
<!-- a separate section to go above the list -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="horizontal">
<!-- your image, you can set it later (see NavDrawerFrag) -->
<ImageView
android:id="#+id/nav_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:src="#android:drawable/ic_menu_myplaces"/>
<!-- a bit of test or a title to go with it -->
<TextView
android:id="#+id/nav_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="Default text"/>
</LinearLayout>
<!-- some divider thing -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:padding="20dp"
android:background="#000000"/>
<!-- your ListView is now a child View -->
<ListView
android:id="#+id/nav_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"/>
</LinearLayout>
Fragment
// new class level members
private ImageView mDrawerImage;
private TextView mDrawerText;
// change the View inflation and extract the new child views
// also modifying the ListView to now be a child instead of the root View
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
// need site names for list
siteNames = getActivity().getResources().getStringArray(R.array.site_names);
Log.d(TAG, "number of sites loaded: " + siteNames.length);
// inflate the parent view (the entire layout)
View view = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
// now grab the separate child views from inside it
mDrawerListView = (ListView) view.findViewById(R.id.nav_listView);
mDrawerImage = (ImageView) view.findViewById(R.id.nav_image);
mDrawerText = (TextView) view.findViewById(R.id.nav_text);
// configure the Views
mDrawerText.setText("Give it a name/title");
//mDrawerImage.setImageURI(...); // set your ImageView however you want, I just gave it one in XML
mDrawerListView.setOnItemClickListener(this);
mDrawerListView.setAdapter(new ArrayAdapter<String>(getActionBar().getThemedContext(),
android.R.layout.simple_list_item_1, android.R.id.text1, siteNames));
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
// and return the inflated view up the stack
return view;
}

Android Parcelable object not passing properly between activities

This has been taxing my mind for the past two days. Oddly, the code works fine in one project, when passing an object from activity to fragment, but I cannot make this object pass from activity to activity, even when I appear to be implementing parcelable properly.
Oddly, I don't get a null object at the other end, but the object has got null values within it.
I've created a new project to test this with. This is the code so far:
MainActivity.java:
package com.example.parcleableexample;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
Site mySite = new Site();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySite.setSiteName("hi");
TextView myText = (TextView) findViewById(R.id.txtSiteName);
myText.setText(mySite.getSiteName());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void doThing (View v) {
Intent mIntent = new Intent(this,AnotherActivity.class);
mIntent.putExtra("site", mySite);
startActivity(mIntent);
}
}
AnotherActivity.java:
package com.example.parcleableexample;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;
public class AnotherActivity extends Activity {
Site mySite = new Site();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySite = getIntent().getParcelableExtra("site");
TextView myText = (TextView) findViewById(R.id.txtSiteName);
myText.setText(mySite.getSiteName());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Site.Java:
package com.example.parcleableexample;
import android.os.Parcel;
import android.os.Parcelable;
public class Site implements Parcelable {
private String siteName;
Site() {
// Empty Constructor
}
public String getSiteName() {
return siteName;
}
public void setSiteName(String siteName) {
this.siteName = siteName;
}
// Parcleable Functions:
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<Site> CREATOR = new Parcelable.Creator<Site>() {
public Site createFromParcel(Parcel in) {
return new Site(in);
}
public Site[] newArray(int size) {
return new Site[size];
}
};
private Site(Parcel in) {
mData = in.readInt();
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="#+id/txtSiteName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/txtSiteName"
android:layout_below="#+id/txtSiteName"
android:layout_marginLeft="42dp"
android:layout_marginTop="25dp"
android:onClick="doThing"
android:text="Button" />
</RelativeLayout>
What I would expect, is for the "hi" value to appear both times, once when I start the app, and again when I click the button and the new activity creates.
However, it's only showing correctly the first time, the second time the getSiteName only returns null.
I also copied and pasted the Site object directly from the working app, with the same result, but it works in the working app as expected; the Site object is copied properly.
The working app runs this code:
Bundle arguments = new Bundle();
arguments.putParcelable("site", SearchResults.get(position));
ViewSiteFragment myFragment = new ViewSiteFragment();
myFragment.setArguments(arguments);
FragmentTransaction myTransaction = getFragmentManager().beginTransaction();
myTransaction.replace(R.id.MainFragmentContainer, myFragment);
myTransaction.addToBackStack(null);
myTransaction.commit();
And picks up the object at the other end here:
Site mSite = getArguments().getParcelable("site");
I know I'm doing something wrong... Just haven't got a clue what I've missed!
The problem is in your implementation of Parcelable. siteName is never written to the parcel, and you are writing mData instead, which is never used anywhere. You can keep mData if you need it, and ADD writes for siteName, see the code:
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
//we want to keep siteName in our parcel, so let's write it to the Parcel obj
out.writeString(siteName);
}
Later, add this code to read from parcel:
private Site(Parcel in) {
mData = in.readInt();
//let's read from in Parcel an set our siteName
siteName = in.readString();
}
Receive it like this with bundle object:
In activity2, in onCreate(), you can get the String message/object by retrieving a bundle (which contains all the messages sent by the calling activity) and call:
Bundle bundle = getIntent().getExtras();
mySite = bundle.getParcelableExtra("site");

Categories