Java tutorial
/* * Copyright 2015 sourcestream GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.sourcestream.movieDB.controller; import android.annotation.SuppressLint; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.net.ParseException; import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Toast; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import de.sourcestream.movieDB.MainActivity; import de.sourcestream.movieDB.MovieDB; import de.sourcestream.movieDB.R; import de.sourcestream.movieDB.adapter.SearchAdapter; import de.sourcestream.movieDB.model.MovieModel; import de.sourcestream.movieDB.model.SearchModel; /** * This class handles the searching. */ public class SearchList extends Fragment implements AdapterView.OnItemClickListener { private MainActivity activity; private ArrayList<SearchModel> searchList; private int totalPages; private SearchAdapter searchAdapter; private String query = ""; private String title; private MovieDetails movieDetails; private CastDetails castDetails; private TVDetails tvDetails; private EndlessScrollListener endlessScrollListener; private Toast toastLoadingMore; private HttpURLConnection conn; private AbsListView listView; private int backState; private Bundle save; private int lastVisitedId; public SearchList() { } /** * Called to do initial creation of a fragment. * This is called after onAttach(Activity) and before onCreateView(LayoutInflater, ViewGroup, Bundle). * * @param savedInstanceState If the fragment is being re-created from a previous saved state, this is the state. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) save = savedInstanceState.getBundle("save"); if (getActivity() != null) ((MainActivity) getActivity()).setSearchViewCount(true); } /** * Called to have the fragment instantiate its user interface view. * * @param inflater sets the layout for the current view. * @param container the container which holds the current view. * @param savedInstanceState If non-null, this fragment is being re-constructed from a previous saved state as given here. * Return the View for the fragment's UI, or null. */ @SuppressLint("ShowToast") @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View rootView = inflater.inflate(R.layout.searchlist, container, false); listView = (AbsListView) rootView.findViewById(R.id.movieslist); listView.setOnItemClickListener(this); toastLoadingMore = Toast.makeText(getActivity(), R.string.loadingMore, Toast.LENGTH_SHORT); activity = ((MainActivity) getActivity()); Tracker t = ((MovieDB) activity.getApplication()).getTracker(); t.setScreenName("Search"); t.send(new HitBuilders.ScreenViewBuilder().build()); return rootView; } /** * @param savedInstanceState if the fragment is being re-created from a previous saved state, this is the state. */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (save != null) { totalPages = save.getInt("totalPages"); setTitle(save.getString("title")); query = save.getString("query"); if (save.getInt("backState") == 1) { backState = 1; searchList = save.getParcelableArrayList("listData"); searchAdapter = new SearchAdapter(getActivity(), R.layout.row, searchList); endlessScrollListener = new EndlessScrollListener(); endlessScrollListener.setCurrentPage(save.getInt("currentPage")); endlessScrollListener.setOldCount(save.getInt("oldCount")); endlessScrollListener.setLoading(save.getBoolean("loading")); } else { backState = 0; if (!query.isEmpty()) search(); } } activity.setTitle(getTitle()); activity.clearSearchCount(); if (searchList == null || searchList.size() == 0) { searchList = new ArrayList<>(); searchAdapter = new SearchAdapter(getActivity(), R.layout.row, searchList); ArrayList<MovieModel> movieList = new ArrayList<>(); int navDrawPos = activity.getOldPos(); if (navDrawPos == 1) { MovieSlideTab movieSlideTab = activity.getMovieSlideTab(); MovieList movieListView = (MovieList) getFragmentManager() .findFragmentByTag(movieSlideTab.getFragmentTag(movieSlideTab.getCurrPos())); if (movieListView != null) movieList = movieListView.getMoviesList(); } if (navDrawPos == 2) { TVSlideTab tvSlideTab = activity.getTvSlideTab(); TVList tvListView = (TVList) getFragmentManager() .findFragmentByTag(tvSlideTab.getFragmentTag(tvSlideTab.getCurrPos())); if (tvListView != null) movieList = tvListView.getTVList(); } if (navDrawPos == 3) { GenresList genres = activity.getGenresList(); movieList = genres.getMovieListView().getMoviesList(); if (movieList == null || movieList.size() == 0) { MovieSlideTab movieSlideTab = activity.getMovieSlideTab(); MovieList movieListView = (MovieList) getFragmentManager() .findFragmentByTag(movieSlideTab.getFragmentTag(movieSlideTab.getCurrPos())); if (movieListView != null) movieList = movieListView.getMoviesList(); } } for (int i = 0; i < movieList.size(); i++) { MovieModel movie = movieList.get(i); SearchModel search = new SearchModel(); search.setId(movie.getId()); search.setTitle(movie.getTitle()); if (movie.getReleaseDate() != null && !movie.getReleaseDate().isEmpty()) search.setReleaseDate(movie.getReleaseDate()); if (movie.getPosterPath() != null && !movie.getPosterPath().isEmpty()) search.setPosterPath(movie.getPosterPath()); if (navDrawPos == 1 || navDrawPos == 3) search.setMediaType("movie"); else search.setMediaType("tv"); searchList.add(search); } } listView.setAdapter(searchAdapter); listView.setOnScrollListener(endlessScrollListener); if (activity.getOldPos() == 1) activity.getMovieSlideTab().showInstantToolbar(); else activity.getTvSlideTab().showInstantToolbar(); } /** * Callback method to be invoked when an item in this AdapterView has been clicked. * * @param parent The AdapterView where the click happened. * @param view The view within the AdapterView that was clicked (this will be a view provided by the adapter) * @param position The position of the view in the adapter. * @param id The row id of the item that was clicked. */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { FragmentManager manager = getFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); boolean result; Bundle args; switch (searchList.get(position).getMediaType()) { case "movie": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (movieDetails != null && lastVisitedId == searchList.get(position).getId() && movieDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash movieDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", searchList.get(position).getId()); Bundle save = movieDetails.getSave(); movieDetails = new MovieDetails(); movieDetails.setTimeOut(0); movieDetails.setSave(save); movieDetails.setArguments(bundle); } else movieDetails = new MovieDetails(); args = new Bundle(); args.putInt("id", searchList.get(position).getId()); movieDetails.setArguments(args); movieDetails.setTitle(searchList.get(position).getTitle()); transaction.replace(R.id.frame_container, movieDetails); result = true; break; case "person": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (castDetails != null && lastVisitedId == searchList.get(position).getId() && castDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash castDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", searchList.get(position).getId()); Bundle save = castDetails.getSave(); castDetails = new CastDetails(); castDetails.setTimeOut(0); castDetails.setSave(save); castDetails.setArguments(bundle); } else castDetails = new CastDetails(); args = new Bundle(); args.putInt("id", searchList.get(position).getId()); castDetails.setArguments(args); castDetails.setTitle(searchList.get(position).getTitle()); transaction.replace(R.id.frame_container, castDetails); result = true; break; case "tv": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (tvDetails != null && lastVisitedId == searchList.get(position).getId() && tvDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash tvDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", searchList.get(position).getId()); Bundle save = tvDetails.getSave(); tvDetails = new TVDetails(); tvDetails.setTimeOut(0); tvDetails.setSave(save); tvDetails.setArguments(bundle); } else tvDetails = new TVDetails(); args = new Bundle(); args.putInt("id", searchList.get(position).getId()); tvDetails.setArguments(args); tvDetails.setTitle(searchList.get(position).getTitle()); transaction.replace(R.id.frame_container, tvDetails); result = true; break; default: result = false; break; } lastVisitedId = searchList.get(position).getId(); if (result) { // add the current transaction to the back stack: transaction.addToBackStack("movieList"); transaction.commit(); // collapse the search View ((MainActivity) getActivity()).collapseSearchView(); } } public void onSuggestionClick(int id, String mediaType, String title) { FragmentManager manager = getFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); boolean result; Bundle args; switch (mediaType) { case "movie": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (movieDetails != null && lastVisitedId == id && movieDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash movieDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", id); Bundle save = movieDetails.getSave(); movieDetails = new MovieDetails(); movieDetails.setTimeOut(0); movieDetails.setSave(save); movieDetails.setArguments(bundle); } else movieDetails = new MovieDetails(); args = new Bundle(); args.putInt("id", id); movieDetails.setArguments(args); movieDetails.setTitle(title); transaction.replace(R.id.frame_container, movieDetails); result = true; break; case "person": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (castDetails != null && lastVisitedId == id && castDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash castDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", id); Bundle save = castDetails.getSave(); castDetails = new CastDetails(); castDetails.setTimeOut(0); castDetails.setSave(save); castDetails.setArguments(bundle); } else castDetails = new CastDetails(); args = new Bundle(); args.putInt("id", id); castDetails.setArguments(args); castDetails.setTitle(title); transaction.replace(R.id.frame_container, castDetails); result = true; break; case "tv": activity.setRestoreMovieDetailsAdapterState(true); activity.setRestoreMovieDetailsState(false); if (tvDetails != null && lastVisitedId == id && tvDetails.getTimeOut() == 0) { // Old movie details retrieve info and re-init component else crash tvDetails.onSaveInstanceState(new Bundle()); Bundle bundle = new Bundle(); bundle.putInt("id", id); Bundle save = tvDetails.getSave(); tvDetails = new TVDetails(); tvDetails.setTimeOut(0); tvDetails.setSave(save); tvDetails.setArguments(bundle); } else tvDetails = new TVDetails(); args = new Bundle(); args.putInt("id", id); tvDetails.setArguments(args); tvDetails.setTitle(title); transaction.replace(R.id.frame_container, tvDetails); result = true; break; default: result = false; break; } lastVisitedId = id; if (result) { // add the current transaction to the back stack: transaction.addToBackStack("movieList"); transaction.commit(); // collapse the search View ((MainActivity) getActivity()).collapseSearchView(); } } /** * This class handles the connection to our backend server. * If the connection is successful we set list results. */ class JSONAsyncTask extends AsyncTask<String, Void, Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); getActivity().runOnUiThread(new Runnable() { public void run() { toastLoadingMore.show(); } }); } @Override protected Boolean doInBackground(String... urls) { try { URL url = new URL(urls[0]); conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000 /* milliseconds */); conn.setConnectTimeout(10000 /* milliseconds */); conn.setRequestMethod("GET"); conn.setDoInput(true); conn.connect(); int status = conn.getResponseCode(); if (status == 200) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } br.close(); JSONObject searchData = new JSONObject(sb.toString()); totalPages = searchData.getInt("total_pages"); JSONArray searchResultsArray = searchData.getJSONArray("results"); for (int i = 0; i < searchResultsArray.length(); i++) { JSONObject object = searchResultsArray.getJSONObject(i); SearchModel movie = new SearchModel(); if (object.has("id") && object.getInt("id") != 0) movie.setId(object.getInt("id")); if (object.has("title")) movie.setTitle(object.getString("title")); if (object.has("name")) { if (object.has("media_type") && object.getString("media_type").equals("tv")) movie.setTitle(object.getString("name")); else movie.setTitle(object.getString("name")); } if (object.has("release_date") && !object.getString("release_date").equals("null") && !object.getString("release_date").isEmpty()) movie.setReleaseDate(object.getString("release_date")); if (object.has("first_air_date") && !object.getString("first_air_date").equals("null") && !object.getString("first_air_date").isEmpty()) movie.setReleaseDate(object.getString("first_air_date")); // is added checks if we are still on the same view, if we don't do this check the program will crash if (isAdded()) { if (object.has("poster_path") && !object.getString("poster_path").equals("null") && !object.getString("poster_path").isEmpty()) movie.setPosterPath(MovieDB.imageUrl + getResources().getString(R.string.imageSize) + object.getString("poster_path")); } if (isAdded()) { if (object.has("profile_path") && !object.getString("profile_path").equals("null") && !object.getString("profile_path").isEmpty()) movie.setPosterPath(MovieDB.imageUrl + getResources().getString(R.string.imageSize) + object.getString("profile_path")); } if (object.has("media_type") && !object.getString("media_type").isEmpty()) movie.setMediaType(object.getString("media_type")); searchList.add(movie); } return true; } } catch (ParseException | IOException | JSONException e) { if (conn != null) conn.disconnect(); } finally { if (conn != null) conn.disconnect(); } return false; } protected void onPostExecute(Boolean result) { // is added checks if we are still on the same view, if we don't do this check the program will cra if (isAdded()) { if (!result) { Toast.makeText(getActivity(), R.string.noConnection, Toast.LENGTH_LONG).show(); backState = 0; } else { getActivity().runOnUiThread(new Runnable() { public void run() { toastLoadingMore.cancel(); } }); backState = 1; if (!searchList.isEmpty()) searchAdapter.notifyDataSetChanged(); else Toast.makeText(getActivity(), R.string.noResults, Toast.LENGTH_LONG).show(); } } } } /** * This class listens for scroll events on the list. */ public class EndlessScrollListener implements AbsListView.OnScrollListener { private int currentPage = 1; private boolean loading = false; private int oldCount = 0; public EndlessScrollListener() { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (oldCount != totalItemCount && firstVisibleItem + visibleItemCount >= totalItemCount) { loading = true; oldCount = totalItemCount; } if (loading) { if (currentPage != totalPages) { currentPage++; loading = false; final JSONAsyncTask request = new JSONAsyncTask(); new Thread(new Runnable() { public void run() { try { request.execute(MovieDB.url + "search/multi?query=" + getQuery() + "?&api_key=" + MovieDB.key + "&page=" + currentPage).get(10000, TimeUnit.MILLISECONDS); } catch (TimeoutException | ExecutionException | InterruptedException e) { request.cancel(true); // we abort the http request, else it will cause problems and slow connection later if (conn != null) conn.disconnect(); toastLoadingMore.cancel(); currentPage--; loading = true; if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { public void run() { Toast.makeText(getActivity(), getResources().getString(R.string.timeout), Toast.LENGTH_SHORT) .show(); } }); } } } }).start(); } else { if (totalPages != 1) { Toast.makeText(getActivity(), R.string.nomoreresults, Toast.LENGTH_SHORT).show(); } loading = false; } } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getOldCount() { return oldCount; } public void setOldCount(int oldCount) { this.oldCount = oldCount; } public boolean getLoading() { return loading; } public void setLoading(boolean loading) { this.loading = loading; } } /** * Fired when the user presses the submit button. * * @param query the query to search */ public void setQuery(String query) { this.query = query.replaceAll("[\\s%\"^#<>{}\\\\|`]", "%20"); search(); } public String getQuery() { return query; } /** * Makes a new request to the server with the given query. */ public void search() { if (getActivity() != null) { listView = (AbsListView) getActivity().findViewById(R.id.movieslist); searchList = new ArrayList<>(); searchAdapter = new SearchAdapter(getActivity(), R.layout.row, searchList); listView.setAdapter(searchAdapter); endlessScrollListener = new EndlessScrollListener(); listView.setOnScrollListener(endlessScrollListener); final JSONAsyncTask request = new JSONAsyncTask(); new Thread(new Runnable() { public void run() { try { request.execute( MovieDB.url + "search/multi?query=" + getQuery() + "?&api_key=" + MovieDB.key) .get(10000, TimeUnit.MILLISECONDS); } catch (TimeoutException | ExecutionException | InterruptedException e) { request.cancel(true); // we abort the http request, else it will cause problems and slow connection later if (conn != null) conn.disconnect(); toastLoadingMore.cancel(); if (getActivity() != null) { getActivity().runOnUiThread(new Runnable() { public void run() { Toast.makeText(getActivity(), getResources().getString(R.string.timeout), Toast.LENGTH_SHORT).show(); } }); } } } }).start(); } } /** * Update the title. We use this method to save our title and then to set it on the Toolbar. */ public void setTitle(String title) { this.title = title; } /** * Get the title. */ private String getTitle() { return this.title; } /** * Called to ask the fragment to save its current dynamic state, * so it can later be reconstructed in a new instance of its process is restarted. * * @param outState Bundle in which to place your saved state. */ @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Used to avoid bug where we add item in the back stack // and if we change orientation twice the item from the back stack has null values if (save != null) outState.putBundle("save", save); else { Bundle send = new Bundle(); send.putInt("totalPages", totalPages); send.putString("title", getTitle()); if (backState == 1) { send.putInt("backState", 1); send.putParcelableArrayList("listData", searchList); // used to restore the scroll listener variables send.putInt("currentPage", endlessScrollListener.getCurrentPage()); send.putInt("oldCount", endlessScrollListener.getOldCount()); send.putBoolean("loading", endlessScrollListener.getLoading()); // Save scroll position if (listView != null) { Parcelable listState = listView.onSaveInstanceState(); send.putParcelable("listViewScroll", listState); } } else { send.putInt("backState", 0); send.putString("query", query); } outState.putBundle("save", send); } } /** * Fired when fragment is destroyed. */ public void onDestroy() { super.onDestroy(); if (getActivity() != null) ((MainActivity) getActivity()).setSearchViewCount(false); } }