Reputation: 2570
In Android, How would one save a text file from the internet to the sdcard, load the file from the sdcard and separate the entries of the file by commas into a ListView?
It's important that more than one of the entries in the ListView can be selected at once. The use of fragments would be nice too, as it would make it easier to use with different screen sizes such as phones vs. tablets.
Upvotes: 0
Views: 1141
Reputation: 2570
I see your question as 2 different problems, each with their own solutions and obstacles, that come together in the end. My examples are all compiled against API16 (4.1 Jelly Bean) with a minimum of API11 (3.0 Honeycomb). - WARNING - large amounts of text incoming.
Loading from the internet seems overwhelming at first, although it is simple in the end. First you want to ensure that the device has a connection. To do this, you create a method called getConnectivityStatus
as seen below:
public boolean getConnectivityStatus() {
ConnectivityManager cm = (ConnectivityManager) this
.getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null)
return info.isConnected();
else
return false;
}
If a connection exists, you'll want to create a directory to save the file and download the file using the DownloadManager class. To do this, simply say:
File directory = new File(Environment.getExternalStorageDirectory(), "ExampleDirectory");
if (!directory.exists())
directory.mkdir();
Next, you'll want to download the file with the method downloadFile(String)
, passing in the file name you'll want. If you want only a single copy of the file at any given time, you'll have to delete the old file if it exists before downloading, or you will have multiple files such as examplefile.txt; examplefile-1.txt; examplefile-2.txt; Place this first portion of code in the method you want to start the download from, such as onClick
:
String FILE_NAME = "examplefile.txt",
File examplefile = new File(Environment.getExternalStorageDirectory()
+ "/ExampleDirectory", FILE_NAME);
if (examplefile.exists()) {
boolean deleted = examplefile.delete();
if (deleted) {
if (getConnectivityStatus())
downloadFile(FILE_NAME);
}
}
The downloadFile(String)
method:
public void downloadFile(String FILE_NAME) {
String url = "http://www.example.com/filetobedownloaded.txt";
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.setDescription("Example file to be displayed.");
request.setTitle(FILE_NAME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
}
request.setDestinationInExternalPublicDir("ExampleDirectory", FILE_NAME);
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
}
You can also register a receiver to return a callback when the download is complete. To do so, simple register the receiver in the onCreate
method like so: registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
and place the below method in your class:
BroadcastReceiver onComplete = new BroadcastReceiver() {
public void onReceive(Context ctxt, Intent intent) {
if (!started) {
started = true;
// perform action upon completion
}
}
};
Here is the final DownloadFile.java
class:
public class DownloadFile extends Activity {
boolean started = false;
String url = "http://www.example.com/filetobedownloaded.txt";
String FILE_NAME = "examplefile.txt",
File directory = new File(Environment.getExternalStorageDirectory(), "ExampleDirectory");
File examplefile = new File(Environment.getExternalStorageDirectory()
+ "/ExampleDirectory", FILE_NAME);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_downloadfile);
registerReceiver(onComplete, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
check();
}
public void check {
if (!directory.exists())
directory.mkdir();
if (!getConnectivityStatus()) {
if (!started) {
started = true;
// perform action if no connection
}
}
if (examplefile.exists()) {
boolean deleted = examplefile.delete();
if (deleted && !started) {
if (getConnectivityStatus())
downloadFile(FILE_NAME);
}
}
}
public boolean getConnectivityStatus() {
ConnectivityManager cm = (ConnectivityManager) this
.getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null)
return info.isConnected();
else
return false;
}
public void downloadFile(String FILE_NAME) {
String url = "http://www.example.com/filetobedownloaded.txt";
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.setDescription("Example file to be displayed.");
request.setTitle(FILE_NAME);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
}
request.setDestinationInExternalPublicDir("ExampleDirectory", FILE_NAME);
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
}
BroadcastReceiver onComplete = new BroadcastReceiver() {
public void onReceive(Context ctxt, Intent intent) {
if (!started) {
started = true;
// perform action upon completion
}
}
};
In order to load the file into a ListFragment, and later display the selected item, you'll have to create 3 classes and 2 xml files in the layout directory. In my example, I will use MainActivity.java
, PreviewFragment.java
, SelectionFragment.java
, activity_main.xml
, and fragment_preview.xml
. We'll start with the xml. The first xml file is the one you are viewing, which contains the two fragments we are working with: the ListFragment and the PreviewFragment. The setup is fairly simple; you specify the two fragment, their ids and constraints, and their respective classes. Here is activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<fragment
android:id="@+id/fragmentSelection"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
class="com.smarticle.catering.update.SelectionFragment" />
<fragment
android:id="@+id/fragmentPreview"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
class="com.smarticle.catering.update.PreviewFragment" />
</LinearLayout>
The above layout is optimized for a tablet in landscape mode. You can tweak the arrangement if you feel so inclined.
Next, you'll have to specify the PreviewFragment in xml, also fairly simple since it is only a TextView centered horizontally and vertically which will eventually display the selected items. Here is fragment_preview.xml
:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/tvPreview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
The ListFragment will be created at runtime, so it will not need its own xml file.
In order to display the fragments on the screen, you'll have to load the activity_main.xml
layout in your activity. This is a very simple task as it looks like every other activity, ever. This is MainActivity.java
:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
That's it, seriously. But now onward. In order to change the text in the PreviewFragment, you'll have to extend the Fragment
class in PreviewFragment.java
, inflate the view and setup a setText method. The PreviewFragment.java
class is shown below:
public class PreviewFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_preview, container,
false);
return view;
}
public void setText(String item) {
TextView tvPreview = (TextView) getView().findViewById(R.id.tvPreview);
tvPreview.setText(item);
}
}
The lifecycle for a fragment can be found here.
Now you'll have to setup the ListFragment. This will be done in the ListFragment.java
class. In the onActivityCreated()
method, you'll want to load the file, ensuring that it actually downloaded and is in the correct directory with the load(String)
method. At this time, you'll also separate the file by its delimiter into an array. This is the load(String)
method:
public void load(String FILE_NAME) {
String[] list;
String FILE_NAME = "examplefile.txt",
File directory = new File(Environment.getExternalStorageDirectory(), "ExampleDirectory");
File examplefile = new File(Environment.getExternalStorageDirectory()
+ "/ExampleDirectory", FILE_NAME);
if (examplefile.exists()) {
try {
File myFile = new File(directory + "/" + FILE_NAME);
FileInputStream fIn = new FileInputStream(myFile);
BufferedReader myReader = new BufferedReader(new InputStreamReader(
fIn));
String aDataRow = "";
String aBuffer = "";
while ((aDataRow = myReader.readLine()) != null) {
aBuffer += aDataRow;
aBuffer = aBuffer.trim();
list = aBuffer.split(",");
}
myReader.close();
if (!loaded)
Toast.makeText(getActivity(),
"Done reading '" + FILE_NAME + "'.", Toast.LENGTH_SHORT)
.show();
loaded = true;
if (!selections.equals("")) {
for (int i = 0; i < selections.size(); i++) {
getListView().setItemChecked(selections.get(i), true);
}
}
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT)
.show();
}
}
}
This will return the String array list
with the contents of examplefile.txt separated by commas. You can replace the commas with whatever delimiter you want, as long as the expression of aBuffer.split(String delimiter) is consistent with the delimiter in the text file. The boolean value loaded
is only to make sure a new Toast doesn't appear everytime the activity is recreated, such as on an orientation change.
In the load(String)
method, it's also a good time to set up your ListFragment's adapter and mode. You'll want to select a textViewResourceId that allows multiple choice, unless you want single choice. This can be done smoothly after the while statement by simple inserting these lines:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getActivity(),
android.R.layout.simple_list_item_activated_1, list);
setListAdapter(adapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
If single choice is desired, change ListView.CHOICE_MODE_MULTIPLE
to ListView.CHOICE_MODE_SINGLE
and change android.R.layout.simple_list_item_activated_1
to android.R.layout.simple_list_item_1
. Alternatively, if you want checks instead of highlighted, change to android.R.layout.simple_list_item_checked
.
In this class, you must also initialize your PreviewFragment, done so in the onActivityCreated
method like so: fragment = (PreviewFragment) getFragmentManager().findFragmentById(
R.id.fragmentPreview);
Lastly, you'll have to be able to read which items are selected in the ListFragment and display them on the PreviewFragment. This I do with a method named getSelectedItems()
shown below:
public void getSelectedItems() {
cntChoice = getListView().getCount();
items = "";
selections.clear();
SparseBooleanArray sparseBooleanArray = getListView()
.getCheckedItemPositions();
for (int i = 0; i < cntChoice; i++) {
if (sparseBooleanArray.get(i) == true) {
items += getListView().getItemAtPosition(i).toString()
+ ";\n";
selections.add(i);
}
}
if (fragment != null && fragment.isInLayout())
fragment.setText(items);
}
The String items
is what is displayed in the TextView, and selections
is an ArrayList<Integer>
used to restore the state upon an orientation change. Normally, you would specify an android:configChanges="orientation"
in the AndroidManifest.xml
file under the <activity >
tag, but the problem occurs when using separate layouts for portrait or landscape. If you allow the Manifest to handle orientation changes, the layout is not changed when the orientation is changed because a new activity is not created like it would be under normal circumstances. Therefor, you create static ArrayList<Integer>
containing the positions which contain a selected item.
The last thing to do is read when a ListItem has been clicked and to call the getSelectedItems
method, a fairly simple task. Insert this below anywhere into your class:
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
getItems();
}
You're finished! Now to put it all together. Here is the ListFragment.java
class:
public class ListFragment extends ListFragment {
String FILE_NAME = "examplefile.txt", items = "";
String[] list;
static ArrayList<Integer> selections = new ArrayList<Integer>();
int cntChoice, position;
static boolean loaded = false;
File directory = new File(Environment.getExternalStorageDirectory(), "ExampleDirectory");
File examplefile = new File(Environment.getExternalStorageDirectory()
+ "/ExampleDirectory", FILE_NAME);
PreviewFragment fragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
fragment = (PreviewFragment) getFragmentManager().findFragmentById(
R.id.fragmentPreview);
check();
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
getSelectedItems();
}
public void getSelectedItems() {
cntChoice = getListView().getCount();
items = "";
selections.clear();
SparseBooleanArray sparseBooleanArray = getListView()
.getCheckedItemPositions();
for (int i = 0; i < cntChoice; i++) {
if (sparseBooleanArray.get(i) == true) {
items += getListView().getItemAtPosition(i).toString()
+ ";\n";
selections.add(i);
}
}
if (fragment != null && fragment.isInLayout())
fragment.setText(items);
}
public void check() {
if (examplefile.exists())
load(FILE_NAME);
}
public void load(String FILE_NAME) {
try {
File myFile = new File(directory + "/" + FILE_NAME);
FileInputStream fIn = new FileInputStream(myFile);
BufferedReader myReader = new BufferedReader(new InputStreamReader(
fIn));
String aDataRow = "";
String aBuffer = "";
while ((aDataRow = myReader.readLine()) != null) {
aBuffer += aDataRow;
aBuffer = aBuffer.trim();
list = aBuffer.split(",");
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getActivity(),
android.R.layout.simple_list_item_activated_1, list);
setListAdapter(adapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
myReader.close();
if (!loaded)
Toast.makeText(getActivity(),
"Done reading '" + FILE_NAME + "'.", Toast.LENGTH_SHORT)
.show();
loaded = true;
if (!selections.equals("")) {
for (int i = 0; i < selections.size(); i++) {
getListView().setItemChecked(selections.get(i), true);
}
getSelectedItems();
}
} catch (Exception e) {
Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT)
.show();
}
}
}
I hope this solved your problem. I know it's lengthy, but it's thorough, tested and works well. To start this, you can do an Intent intent = new Intent(getBaseContext(), MainActivity.class); startActivity(intent); finish();
in the onReceive
method of the receiver in the DownloadFile.java
class. I would also suggest placing those same lines of code in the check()
method, specifically the portion that is called if there is no connection, in which case, it will load the file that was previously downloaded to the directory. Good luck, happy coding and always remember... 01101000011101000111010001110000011100110011101000101111001011110111011101110111011101110010111001111001011011110111010101110100011101010110001001100101001011100110001101101111011011010010111101110111011000010111010001100011011010000011111101110110001111010110100101110101011000100100101000101101010110000101001101001100001110010110011101101111
Upvotes: 2