I want a create a quiz app in android in which I want to dynamically change the questions for every 60 seconds in the same layout where the number of question vary dynamically.
I planned to do a quiz system which creates layout dynamically and changes it between some interval.
I tried with a countdown Timer but it's not accurate.
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_student_main);
String[] fr={"National Bird Of India?","How is your life?","What's yourFavourite dish","Whats your hobby","Whats your skin color"};
String s="";
int i;
i=0;
int seconds=10000;
CountDownTimer countDownTimer = new CountDownTimer((fr.length*seconds)+seconds, seconds)
{
#Override
public void onTick(long l)
{
String s = String.valueOf(l);
char[] f = s.toCharArray();
textView.setText(String.valueOf(i+1));
textView2.setText(fr[i]);
if(!s.equals(String.valueOf(f[0])))
{
textView2.setText(fr[i]);
i++;
}
else
{
s=String.valueOf(f[0]);
}
}
#Override
public void onFinish()
{
Intent intent=new Intent(getBaseContext(),Finish_quiz.class);
startActivity(intent);
finish();
}
};
countDownTimer.start();
}
Your question is very vague but looks like fragments are what you need. Assuming you know about Fragments in Android, they are designed for the exact purpose of changing your UI look and feel on the go within the same container (which in Android terminology is the Activity component).
So consider you have 50 questions and all of them roughly falls under 5 segments requiring you to design 5 different layouts. So what you do is define 5 different fragments with their distinct look and feel. Albeit you can always reuse individual UI components among fragments which is what you should do, thus requiring you to write lesser code.
Now when you want to display a different UI for the next question, you can just replace the current fragment in the UI. For performance sake, you can create instance of all the 5 fragments type in your activity start and just go on replacing the current viewable fragment.
Please read upon - https://developer.android.com/training/basics/fragments/fragment-ui.html
Related
I have a main activity which has 2 fragments. The main activity has a SearchView in the action bar. Both the fragments have a list of large number of strings, List<String>.
The flow is:
User enters Fragment I --> Selects a string (lets say Selection1) --> Based on Selection1 a list of strings is populated in the second fragment --> Here the user selects a second String ---> Processing based on these two strings.
Now since both the fragments contain a large number of strings, the user enters a query in the SearchView, which filters the list and reduces it to a smaller list displayed in the SearchableActivity.
Now the problem is how does the SearchableActivity get access to these two List<String> to filter them based on the query and display a reduced list to the user.
Currently what I have done is overridden onSearchRequested and pass the data as
#Override
public boolean onSearchRequested()
{
Bundle appData = new Bundle();
appData.putString(FRAGMENT_ID, "Fragment_A");
appData.putStringArrayList(SEARCH_LIST, searchList);
startSearch(null, false, appData, false);
return true;
}
Is there a better way or standard way by which this problem can be handled i.e. an implementation that allows data to be based from my MainActivity to SearchableActivity?
Edit: Adding code. Showing how data is set in the Fragment. onDataReceived is called from the HttpManager which receives the data.
#Override
public void onDataReceived(String type,final Object object)
{
switch(type)
{
case PopItConstants.UPDATE_LIST:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run()
{
updateCinemaList((List<String>) object);
}
});
break;
}
}
public void updateDataList(List<String> data)
{
this.dataList = data;
spinner.setVisibility(View.GONE);
mAdapter.updateList(dataList);
}
I just answered a similar question a few minutes ago, at how can I send a List into another activity in Android Studio
I encourage you to rethink your pattern of simply passing data around among Activities and Fragments. Consider creating one or more data models (non-Android classes) for your application, and making these models available to the Android classes (Activities, Fragments, etc.) that need them.
Remove all of the data storage and manipulation code from your Activities and Fragments, and put it into the model(s).
Okay... So this is how I did it.
Basically, the data received in the two fragments was not simply List<String> but they were models viz. Cinema and Region which contained details other than names including location, rating etc.
So, firstly, I made an interface ISearchable
public Interface ISearchable
{
// This contains the Search Text. An ISearchable item is included
// in search results if query is contained in the String returned by this method
public String getSearchText();
//This is meant to return the String that must be displayed if this item is in search results
public String getDisplayText();
//This is meant to handle onClick of this searchableItem
public void handleOnClick();
}
Both the Cinema and Region models implemented ISearchable.
After this, I used a singleton class DataManager in which I maintained a List<ISearchable> currentSearchList.
public class DataManager
{
.....<singleton implementation>....
List<ISearchable> currentSearchList;
public void setSearchList(List<ISearchable> searchList)
{
this.currentSearchList = searchList;
}
public List<ISearchable> getSearchList()
{
return this.currentSearchList;
}
}
So whenever a fragment (either Fragment_A or Fragment_B) is loaded, it updates this currentSearchList, so that when the SearchableActivity performs search all it has to do is DataManager.getInstance().getSearchList() and then use this list for filtering out a list of matching items.
This is how I handled the problem of having Lists in Activity other than the SearchableActivity using which search needs to be performed.
I understand this might not be the best solution, so, I look forward to suggestions and criticisms, and using that to be arrive at a better solution.
I am asking this question because, this is quiet no clear to me why my graph does not get update each time the user select to parse data for 1day or 1week or 1month basis. A similar How to update/remove/insert new series/data in Shinobi Line Chart in android?
I attempted the answer provided by a member called Kai, apparently he works for shinobicontrols.
You may also also note that I have implemented the shinobichart library inside a GraphFragment that seats in a view pager, which imports android.android.support.v4.app.Fragment;
ViewPagerAdapter class imports
import android.support.v4.app.FragmentPagerAdapter;
and the fragment transaction that calls the GraphFragment class budles an array of graph data from another activity that parses the JSON graphdata. I am trying to make this question clear so that when you read my code atleast you get an idea that the problem is not the JSON data because is pulled accordingly based on 1week, 1day or 1month. The issues is that Shinobichart does remove the series and its data but does not plot new parsed data. I read shinobichart user-guide how to handle chart-life cycle but was unable to find the solution I want. I also read ChartFragment handle onPause and onResume for the developer and I wonder If the same applies to SupportChartFragment.
Here is my GraphFragment that integrates shinobichart.Hope someone can help. Thank you in advance.
public class GraphFragment extends Fragment implements OnClickListener,
ShinobiChart.OnCrosshairActivationStateChangedListener{
private static final int CROSSHAIR_INACTIVE_COLOR = Color.argb(255, 240, 240, 240);
private static final int CROSSHAIR_ACTIVE_COLOR = Color.argb(150, 0, 0, 0);
Context context;
String label_x[];
ArrayList<DataAssetGraph> alAssetGraph = new ArrayList<DataAssetGraph>();
Button btnOneDayG, btnOneWeekG, btnOneMonthG;
String endDate;
String assetId;
ProgressDialog dialog;
ShinobiChart shinobiChart;
LineSeries series;
SupportChartFragment chartFragment;
String startDate;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = null;
view = inflater.inflate(R.layout.fragment_chart, null);
initView(view);
if (getArguments() != null) {
alAssetGraph = (ArrayList<DataAssetGraph>) getArguments()
.getSerializable(WebElement.RECORDS);
if (alAssetGraph != null && alAssetGraph.size() > 0) {
// DrawGraph(alAssetGraph);
// Only setup the chart the first time the activity is created
if (savedInstanceState == null) {
// Log.d("Init Graph", "Retrieve"+ alAssetGraph);
chartFragment =
(SupportChartFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.chart);
shinobiChart = chartFragment.getShinobiChart();
// TODO: replace <license_key_here> with you trial license key
shinobiChart.setLicenseKey("sCVfKPnWajLtffqMjAxNTA0MThzdGVybmx5QHJpZ2h0Y2xpY2t" +
"tZWRpYS5jby56YQ==rveipQf9y4819/K4wLwWKR86Q1RIViUBTLEhBXAwh6q5zW53TgYi" +
"JcIUvc3S7DhTfH4KzUNeol9Rc5rXrzLOBnzP0TStc8n+eytCBhUFEgR21Cv7gq1dLEvOu" +
"tLENUwUtZ6Crk+Z8syIKEuyfZ8/1gtPvHIc=BQxSUisl3BaWf/7myRmmlIjRnMU2cA7q+" +
"/03ZX9wdj30RzapYANf51ee3Pi8m2rVW6aD7t6Hi4Qy5vv9xpaQYXF5T7XzsafhzS3hbBo" +
"kp36BoJZg8IrceBj742nQajYyV7trx5GIw9jy/V6r0bvctKYwTim7Kzq+YPWGMtqtQoU=" +
"PFJTQUtleVZhbHVlPjxNb2R1bHVzPnh6YlRrc2dYWWJvQUh5VGR6dkNzQXUrUVAxQnM5b2" +
"VrZUxxZVdacnRFbUx3OHZlWStBK3pteXg4NGpJbFkzT2hGdlNYbHZDSjlKVGZQTTF4S2Zwe" +
"WZBVXBGeXgxRnVBMThOcDNETUxXR1JJbTJ6WXA3a1YyMEdYZGU3RnJyTHZjdGhIbW1BZ21PTT" +
"dwMFBsNWlSKzNVMDg5M1N4b2hCZlJ5RHdEeE9vdDNlMD08L01vZHVsdXM+PEV4cG9uZW50Pk" +
"FRQUI8L0V4cG9uZW50PjwvUlNBS2V5VmFsdWU+");
// Create the series
createLineSeries(alAssetGraph);
// Add this Activity as a listener for any crosshair changes
shinobiChart.setOnCrosshairActivationStateChangedListener(this);
}
} else {
}
}
return view;
}
private void initView(View view)
{
btnOneDayG=(Button)view.findViewById(R.id.btnOneDayG);
btnOneWeekG=(Button)view.findViewById(R.id.btnOneWeekG);
btnOneMonthG=(Button)view.findViewById(R.id.btnOneMonthG);
btnOneDayG.setSelected(true);
btnOneDayG.setOnClickListener(this);
btnOneMonthG.setOnClickListener(this);
btnOneWeekG.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnOneDayG:
btnOneWeekG.setSelected(false);
btnOneMonthG.setSelected(false);
if(!btnOneDayG.isSelected())
{
btnOneDayG.setSelected(true);
startDate=GlobalData.getDateBeforeOneDay();
getGraphHistory(startDate);
System.out.println("Btn Date 1 day: %tc"+startDate);
}
break;
case R.id.btnOneWeekG:
btnOneDayG.setSelected(false);
btnOneMonthG.setSelected(false);
if(!btnOneWeekG.isSelected())
{
btnOneWeekG.setSelected(true);
startDate=GlobalData.getDateBeforeOneWeek();
getGraphHistory(startDate);
System.out.println("Btn Date 1 week: %tc"+startDate);
}
break;
case R.id.btnOneMonthG:
btnOneWeekG.setSelected(false);
btnOneDayG.setSelected(false);
if(!btnOneMonthG.isSelected())
{
btnOneMonthG.setSelected(true);
startDate=GlobalData.getDateBeforeOneMonth();
getGraphHistory(startDate);
System.out.println("Btn Date 1 Month: %tc"+startDate);
}
break;
default:
break;
}
}
private void createLineSeries(ArrayList<DataAssetGraph> alGraph) {
// TODO Auto-generated method stub
shinobiChart.getSeries();
// remove Series
while (shinobiChart.getSeries().size() > 0) {
shinobiChart.removeSeries(shinobiChart.getSeries().get(0));
}
// remove Axis
while (shinobiChart.getAllXAxes().size() > 0) {
shinobiChart.removeXAxis(shinobiChart.getAllXAxes().get(0));
}
while (shinobiChart.getAllYAxes().size() > 0) {
shinobiChart.removeYAxis(shinobiChart.getAllYAxes().get(0));
}
// Create the X-axis, showing ticks daily with a custom format and
// clipping the tick at the far right
DateTimeAxis xAxis = new DateTimeAxis();
// xAxis.setTitle("Date/Time");
xAxis.enableGesturePanning(true);
xAxis.enableGestureZooming(true);
xAxis.getDoubleTapBehavior();
// Create the Y-axis, clipping the tick at the top
NumberAxis yAxis = new NumberAxis();
// yAxis.setTitle("Temperature");
yAxis.enableGesturePanning(true);
yAxis.enableGestureZooming(true);
// Declare length of graph array
int length=alGraph.size();
LineSeries series = new LineSeries();
series.getStyle().getPointStyle().setPointsShown(false);
DataAdapter<Date, Double> data = new SimpleDataAdapter<Date, Double>();
for(int i=0;i<length;i++)
{
String dateString=alGraph.get(i).x_cord;
double y_cord= alGraph.get(i).y_cord;
Date x_cord=convertToDate(dateString);
data.add(new DataPoint<Date, Double>(x_cord, y_cord));
}
// reload and redraw the graph
series.setDataAdapter(data);
series.setCrosshairEnabled(true);
shinobiChart.addSeries(series, xAxis, yAxis);
series.getStyle().setLineColor(Color.WHITE);
System.out.println("Add Series");
// Style the chart and the crosshair
shinobiChart.getStyle().setPlotAreaBackgroundColor(
GraphFragment.CROSSHAIR_ACTIVE_COLOR);
shinobiChart.getCrosshair().getStyle().setLineColor(Color.BLUE);
shinobiChart.getStyle().setBackgroundColor(Color.WHITE);
// shinobiChart.getStyle().setPlotAreaBackgroundColor(Color.BLACK);
shinobiChart.getStyle().getBackgroundColor();
shinobiChart.getXAxis().getStyle().getGridlineStyle().setGridlinesShown(true);
shinobiChart.getYAxis().getStyle().getGridlineStyle().setGridlinesShown(true);
// Remember to redraw the chart to make the changes visible
shinobiChart.redrawChart();
}
private Date convertToDate(String dateString)
{
Date convertedDate= new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try
{
convertedDate = dateFormat.parse(dateString);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
return convertedDate;
}
private void getGraphHistory(String start_date)
{
System.out.println("Get graph History: %tc"+ start_date);
dialog= new ProgressDialog(getActivity());
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setMessage("Retrieving graph...");
dialog.setCanceledOnTouchOutside(false);
dialog.show();
endDate=GlobalData.getCurrentDate();
assetId=ComponentActivity.assetId;
new LoadAssetGraphTask(getActivity(),assetId, start_date,endDate)
{
#Override
protected void onPostExecute(ArrayList<DataAssetGraph> result)
{
super.onPostExecute(result);
if(dialog.isShowing())
dialog.dismiss();
if(result!=null && result.size()>0)
{
createLineSeries(result);
System.out.println("onPostExecute Called");
}
};
}.execute();
//}
}
#Override
public void onCrosshairActivationStateChanged(ShinobiChart chart) {
// Set the plot area background color depending on the crosshair's
// activation state
if (chart.getCrosshair().isActive()) {
chart.getStyle().setPlotAreaBackgroundColor(GraphFragment.CROSSHAIR_ACTIVE_COLOR);
chart.getLegend().getStyle().setTextColor(Color.WHITE);
}
else {
chart.getStyle().setPlotAreaBackgroundColor(GraphFragment.CROSSHAIR_INACTIVE_COLOR);
chart.getLegend().getStyle().setTextColor(Color.BLACK);
}
// Remember to redraw the chart to make the color change visible
chart.redrawChart();
}
I appreciate that it is some time since you have asked this. As it is unanswered, I will try to provide an answer, also in case other people ask similar questions.
Can you please post the rest of your code as it is difficult to get the full picture of what your code is doing? For example your layout files, Activity file and your LoadAssetGraphTask file.
In the meantime, I have created a simple application which has one main activity which contains a ViewPager. I have extended SupportChartFragment and I hold 3 charts in the ViewPager. I have 3 buttons in my Activity, which load 3, 6 and 12 months of data to the chart.
I have kept my data quite simple for the purposes of this exercise, I simply hard code it.
I was successful in being able to dynamically re-load my data upon clicking the buttons.
You can see my app on GitHub here:
https://github.com/Kai2k/ViewPagerChartFragment
I can make several observations about your code:
To reload your chart you do not necessarily need to remove your axes and the Series. Having said that, every time you add a data point to your DataAdapter, a full draw of the chart is invoked which may hamper performance. As such you may like to detach your DataAdapter from your Series, update your data and then re-attach it.
I notice you have made your Fragment implement OnClickListener. I noticed using this approach the chart was not initially updated, but in fact another chart within the ViewPager (which was currently off-screen) was updated instead. I notice that as I paged though my pages within the ViewPager, all of the contained charts were updated. At this point in time I am no expert on how the ViewPager class internally handles the creation and destruction of fragments, but this may certainly be an area to investigate further.
When I set the click listener in the Activity and 'push' the command to reload to the current fragment, it works.
You may also have an issue with your LoadAssetGraphTask, which I believe is an AsyncTask. Obviously I cannot see this code at present so I do not know what this class will be doing. Have you tried a simpler approach first, with dummy data within your Fragment (as I have) to rule out any issue with the AsyncTask?
SupportChartFragment and ChartFragment do handle life cycle call backs for you, so you do not need to override onPause or onResume. You may have issues however if you try to nest your Fragment inside another Fragment, because ChartFragment/SupportChartFragment is retained across Activity re-creation and the Android framework does not allow retained Fragments within other Fragments. If your use-case dictates this you may find using a ChartView a more suitable approach. In this case you would need to handle life cycle call backs.
If you wish to use the ChartFragment or SupportChartFragment, another approach might be to extend the class directly, rather than extend Fragment. This is the approach which I have taken in my app. With this approach you are less likely to encounter inflation issues when inflating nested fragments.
I hope that this helps. Thanks, Kai.
Disclaimer - I work for Shinobicontrols.
I started making a project where there are goats! Yeah Goats.
Currently there is only one function, when I click a goat, it create another goat in a Random position.
I realized that there is a pattern of positions:
Here is the code:
public class GameActivity extends Activity {
private int[] arrGoats = new int[5];
private RelativeLayout battlefield;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
battlefield = (RelativeLayout) findViewById(R.id.rel_battlefield);
arrGoats[0] = R.drawable.amarelo;
arrGoats[1] = R.drawable.azul;
arrGoats[2] = R.drawable.branco;
arrGoats[3] = R.drawable.verde;
arrGoats[4] = R.drawable.vermelho;
criarCabra(60, 100);
}
private void criarCabra(float x, float y) {
int cabraImg = arrGoats[new Random().nextInt(4)];
ImageView cabra = new ImageView(this);
cabra.setImageResource(cabraImg);
cabra.setX(x);
cabra.setY(y);
LayoutParams params = (LayoutParams) new LayoutParams(MarginLayoutParams.WRAP_CONTENT,
MarginLayoutParams.WRAP_CONTENT);
params.width = 150;
params.height = 120;
cabra.setLayoutParams(params);
cabra.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
criarCabra(new Random().nextInt(2000), new Random().nextInt(1000));
}
});
battlefield.addView(cabra);
}
}
I would like to know why this pattern is being created although I'm using Random().NextInt() to define goats positions.
Am I crazy?
First, you're creating a new Random object each time. In Android, the initial seed is derived from current time and the identity hash code:
public Random() {
// Note: Using identityHashCode() to be hermetic wrt subclasses.
setSeed(System.currentTimeMillis() + System.identityHashCode(this));
}
For two objects created in sequence, the identity hash codes are close to each other. On my Android KitKat Dalvik VM, I get identity hash codes that differ only by 32.
The currentTimeMillis() does not provide much difference to the seeds either.
The random itself is a linear congruential generator of the form
random[i+1] = a * random[i] + b (mod c)
where random[0] is the seed and a, b and c are parameters.
Based on this answer, similar seeds indeed produce similar results in linear congruential generators:
The reason you're seeing similar initial output from nextDouble given similar seeds is that, because the computation of the next integer only involves a multiplication and addition, the magnitude of the next integer is not much affected by differences in the lower bits.
Hence, your two successively generated Randoms with default seeds will produce values that seem to be correlated and make your goats get positioned on a line.
To fix it, use the same Random object and/or a more random pseudorandom generator than a linear congruential one.
You are creating new instances of Random with every call to criarCabra and every invocation of onClick. Create a single static instance of Random, then re-use it.
Unless you really know what you're doing and have a very good reason to be doing it, the best practice is to only create one instance of Random per program and then poll it whenever you need additional values.
This is probably very odd, but I'm using multiple CursorLoaders in Android to do multiple queries and in the onLoadFinished(), I am adding views like TextViews and ListViews to my layout dynamically based on cursor results like if the cursors were not null. I do get accurate results, but since I'm using AsyncTaskLoader (CursorLoader), the cursor results don't come in at the same time and the results are not added in the correct order. I previously used a static layout and added views at indices and did view.setVisiblity(View.GONE) based on the results, but it was just too much and too confusing because I have like 32 views. Plus it seemed weird because I don't think the user wants to see all of those views going away and moving up and down based on AsyncTaskLoader results.
How can I get the views in the correct order I want them in without having a bunch of boolean variables? I looked into LayoutInflater but that requires indices as well, but I'm not sure that will help me. The problem with indices for me is that in cursorloader ID 1:
view.addView(v, 1);
view.addView(v, 2);
might not get executed until the cursorloader with ID 2 finishes with:
view.addView(v, 3);
view.addView(v, 4);
If cursorLoader ID 1 doesn't get executed and ID 2 does, then there is missing space and I have to do a ton of view.setVisibility(View.GONE) if I use static XML views and do not dynamically add them.
In the code I'm doing something like this currently:
#Override
public void onLoadFinished(android.support.v4.content.Loader<Cursor> cursorLoader, Cursor cursor) {
switch (cursorLoader.getId())
{
case 0:
if (cursor != null && cursor.moveToFirst()) {
..
title = new TextView(this);
...
mainLinearLayout.addView(title, 1);
}
break;
case 1:
if (cursor != null && cursor.moveToFirst()) {
..
title2 = new TextView(this);
mainLinearLayout.addView(title2, 2);
break;
default:
...
}
}
I also read somewhere online that it is better to use a service instead of cursorloader if you want to do queries on the background thread and have them finish in a certain order, but I have not heard that advice anywhere else or seen any examples doing queries in services. They all use CursorLoader. Is this advice necessarily true? Sounds a bit sketchy.
By the way, I am using the CursorLoader implementation without a content provider given at CursorLoader usage without ContentProvider
How can I get the views in the correct order I want them in without
having a bunch of boolean variables?
You do need some sort of status control in order to make the views appear in order. I would delegate the view construction/addition to a control class that will have all the information required to make the correct view and in the right order no matter how the loaders finished their jobs.
public class ViewDispatcher {
public SparseArray<Status> mLoadStatuses = new SparseArray<Status>();
public SparseArray<Cursor> mDataCursors = new SparseArray<Cursor>();
// you'll call this method for each of the loaders, in the order they should be. The ids should be incremental
public void registerLoader(int loaderId) {
mLoadStatuses.put(loaderId, Status.INITIAL);
}
// called when one of the loaders finishes its job
public void onLoadComplete(int loaderId, Cursor data) {
mDataCursors.put(loaderId, data);
boolean current = true;
mLoadStatuses.put(loaderId, Status.LOADED);
if (loaderId == firstLoaderId) {
// the first loader it's done and we should start the view creation right away
buildView(loaderId, mainLayout, true);
mLoadStatuses.put(loaderId, data, Status.FULLY_BUILT);
} else {
// implement a priority system, a view construction will be triggered ONLY
// if the previous loader has finished loading data and its view is in place
// I'm assuming that the Loaders have consecutive ids
if (mLoadStatuses.get(loaderId - 1) != null && mLoadStatuses.get(loaderId - 1) == Status.FULLY_BUILT) {
buildView(loaderId, data, mainLayout, true);
mLoadStatuses.put(loaderId, Status.FULLY_BUILT);
} else {
current = false;
}
}
// we'll also need to implement a buddy system. When a loader is done loading and its view
// is created we must check to see if we don't have other loaders after this current one that have finished loading
// but couldn't construct their view because this current loader didn't finished at that moment
// get the next loader
int next = loaderId + 1;
while(current && next < totalNumberOfLoaders && mLoadStatuses.get(next) == Status.LOADED) {
// continue to build views
buildView(next, mDataCursors.get(loaderId), mainLayout, true);
mLoadStatuses.put(next, Status.FULLY_BUILT);
next++;
}
}
// this will build the appropriate view, and optionally attach it
public void buildView(int loaderId, Cursor data, view mainLayout, boolean attach) {
// build the view for this specific Loader
}
}
public enum Status {
INITIAL, LOADED, FULLY_BUILT
}
I hope I'm not missing something obvious as I wrote that without any tests. To use it, you'll first call the registerLoader() method for all loaders in the order you need them to be and in the onLoadComplete() callback of the LoaderCallbacks call ViewDispatcher.onLoadComplete().
I also read somewhere online that it is better to use a service
instead of cursorloader if you want to do queries on the background
thread and have them finish in a certain order, but I have not heard
that advice anywhere else or seen any examples doing queries in
services.
You've probably read about IntentService which can be made to follow a queue through the order of the Intents it receives. But, I don't see how this would help you as it would just add problems. For one you use Cursors as the data holders that you would need to pass back and you need to create views which the IntentService can't do(it will need to make the Activity create them through various communication ways, this is unnecessary work from my point of view).
in my android application at some event in an activity I want to ask the user for a name (string). I know how to do this: call showDialog, create the dialog in the Activity.onCreateDialog method (I need to supply a string for the label) and handle the result in the onClick of the dialog. This works fine and to my satisfaction.
BUT this way I have three different places, where this simple task spreads throughout the code of my activity. I would much more prefer to keep this code together, to write some code like this:
string result;
if (showSimpleEditDialog(idForLabelString, result)==DIALOG_OK)
{
// do something with the result
}
or maybe with a class instance
SimpleEditDialog dlg = new SimpleEditDialog(idForLabelString);
if (dlg.showModal()==DIALOG_OK)
{
string result = dgl.getResult();
// do something with the result
}
(The "idForLabelString" would be some resource id for the label to use, DIALOG_OK would be some constant returned when the user clicks OK)
I know, I would have to write this methodes or this class. But for better readibility of my code I would do it. Any suggestions?
Thank you,
Gerhard
"BUT this way I have three different places, where this simple task spreads throughout the code"
So why don't you create a Method for this task? What you are talking about sounds like some sort of 'ActionListener' to me. This can be done in Java/Swing, but not in Android.
But, if you have three Dialogs, which all need to do the same when "YES" e.g. "NO" is pressed, you could define the 'DialogInterface.OnClickListener()' as a global inner-Class (or in a second class which extends the 'onClickListener') and then use it for all the Dialogs.
Now actually the problem with modal dialogs is mostly a problem with programm flow. You want to keep things together that belong together. You want to display a dialog that returns "ok" or "cancel" and additionaly e.g. a string that the user entered into one of the dialog widgets.
I do not want to write half of the code up to the line where I need the result of the dialog on one place and the rest of the code on another place namely the onClickListener of the dialog.
In some scenarios the first dialog might invoke a second dialog e.g. to specify a color which is not in the list of the first dialog's ListView.
Your code will be scattered all over the place (in each dialog's button onClickListener) and will be hard to read or to maintain.
Now after having written some unclear code like that I came up with the following solution which certainly respects the android design guides.
Instead of directly showing a dialog I create a Handler derived class which handles messages.
I send it a first message which creates and shows a dialog. It also forwards the handler to the dialog and the diaolg in it's onStop method sends another message to the handler, indicating the end of the dialog. There you can examine the dialogs properties, the contents of the edit fields or whether it was stopped with OK or CANCEL.
Now in the message handler all the logic of the task sits in different cases of the messages arg1 value.
Some cases might be skipped (e.g. the user selected a standard color and did not need a special color dialog).
The dialogs are independant of the scenario from which they are called and in their code only reflect their simple task (selecting from a list, some checkboxes etc.). They may be reused from other scenarios.
Following a kind of a template how to use this approach:
public class DoSomethingWithDialogs extends Handler
{
Context context; // from which it was called
final static int stepBegin = 0;
final static int stepNext = 1;
final static int stepSomethingElse = 2;
final static int stepLast = 3;
protected DoSomethingWithDialogs(Context context)
{
this.context = context;
}
public static void start(Context context)
{ // this is the main (only) entry point from outside
DoSomethingWithDialogs st = new DoSomethingWithDialogs(context);
st.sendMessage(st.obtainMessage(0, stepBegin, 0));
}
#Override
public void handleMessage(Message msg)
{
// step by step handling the task
switch (msg.arg1)
{
case stepBegin:
{
SomeDlg somedlg = new SomeDlg(context, this, stepNext);
// when the dialog closes, it sends a message to this with stepNext as arg1
somedlg.show();
}
break;
case stepNext:
{ // this message was send by the dialog when it finished
SomeDlg somedlg = (SomeDlg) msg.obj;
if (msg.arg2 == Dialog.BUTTON_NEGATIVE)
{
// has been canceled, nothing to do
} else
{
if (somedlg.someProperty)
{
} else
{
sendMessage(obtainMessage(0, stepSomethingElse, 0));
}
}
}
break;
case stepSomethingElse:
break;
}
}
}