I have a button which calls a long running function which runs some kind of loop:
void runLongRunningFunction() {
for (int i = 0; i < 100000; i++) {
doSth();
}
}
...
onPressed: () {
runLongRunningFunction();
}
Now I want to reach two goals:
The function should not block the UI: Currently the UI freezes as long as the function runs.
Instead it should show some kind of progress, maybe a Text() widget which updates a percentage or even better, a progress bar.
So:
How can I decouple the function from the UI?
How can I return some intermediate values from the function calculation (e.g. the current value of the iterator i) to the UI to update my progress view while the function is still running?
Thanks in advance!
You should use FutureBuilder widget.
Future<String> _runLongRunningFunction() async {
String result;
for (int i = 0; i < 100000; i++) {
...
}
return result;
}
FutureBuilder<String>(
future: runLongRunningFunction(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
Widget child;
if (snapshot.hasData) {
child = Text(snapshot.data);
} else if (snapshot.hasError) {
child = Text('${snapshot.error}');
} else {
child = CircularProgressIndicator();
}
return Center(
child: child
);
},
);
Related
guys
I'm trying to Create a list to add and delete items which are already stored in my database(MongoDB). I already created an Animated list that once its clicked on it deletes the item but as soon as i add the function to remove the item from the database it begins to show a loader as I used a ListView.builder
FutureBuilder(
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none &&
projectSnap.hasData == null) {
//print('project snapshot data is: ${projectSnap.data}');
return Text('Nothing To show Here');
}else if(
projectSnap.connectionState == ConnectionState.waiting
) {
return Center(child: CircularProgressIndicator());
} else if(
projectSnap.connectionState == ConnectionState.active
) {
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
else{
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
},
future: fetchlist(),
),
This is the code for the list
void _removeSingleItems(int index) {
int removeIndex = index;
String removedItem = pair.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the original
// has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, index, animation);
};
_listKey.currentState?.removeItem(removeIndex, builder);
}
This is the code to delete the item using Animated list
This is the how it works
So what I actually need is just a better alternative if listview.builder won't do the job.
Cause whenever I delete it loads again which I don't want
Here is the code. If a View has been long clicked, I want this loop to rerun by making i = 0. But the if statement after setOnLongClickListener only gets executed once in the beginning and not after the view has been long clicked.
final hasLongClicked[] = {false};
for(int i = 0; i < mLLayout.getChildCount(); i++){
// tried declaring hasLongClicked[] here but no avail
View child = mLLayout.getChildAt(i);
child.setOnLongClickListener(new View.OnLongClickListener(){
#Override
public boolean onLongClick(View v) {
//Some stuff
hasLongClicked[0] = true;
return false;
}
});
if(hasLongClicked[0])
i = 0;
}
How do I do get through this? On a separate note, is this a good way to setOnLongClickListeners to all child views of a linear layout?
Help is much appreciated. Thank you
I solved it by making a function out of it and calling itself whenever the event has been handledas follows:
private void discardEvents(LinearLayout mLLayout) {
for(int i = 0; i < mLLayout.getChildCount(); i++){
View child = mLLayout.getChildAt(i);
child.setOnLongClickListener(new View.OnLongClickListener(){
#Override
public boolean onLongClick(View v) {
//Do your stuff
discardEvents(mLLayout);
return false;
}
});
}
}
Although I would like to know if this would cause any problems/ has hidden bugs.
I am writing a function to compare if the string contains a list of keywords (string list)
I implement it as a callback function from another widget in flutter.
The callback function works, however, the compare method inside callback function doesn't work.
Please send help /.\
void callback(List<String> filterKeyword) {
setState(() {
this.filterKeyword = filterKeyword;
});
resFiltered.clear();
res.forEach((res) {
if (compareString(res.type, this.filterKeyword)) {
resFiltered.add(res);
}
});
}
bool compareString(String inputStr, List<String> items) {
if (items.length == 0) {
return true;
}
for(int i = 0; i < items.length; i++) {
if(inputStr.contains(items[i])) {
return true;
}
}
return false;
}
I changed it little bit to return string and implemented added to flutter default project as text widget. And it worked. You have to ensure if the parameters where indeed as you written... I notice that this is case sensitive...
I hope it will help!
I have a longish running operation that I want to run synchronously (I will look at async later on).
I am using the overlay code from here - http://docs.xamarin.com/recipes/ios/standard_controls/popovers/display_a_loading_message
Here is my test code -
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds);
View.Add (loadingOverlay);
int i = 0;
while (i < 5000)
{
Console.WriteLine(i);
i++;
}
loadingOverlay.Hide ();
}
But the initial overlay does not show, only the Hide animation after the while loop completes?
I can produce a hack / workaround like so -
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds);
View.Add (loadingOverlay);
NSTimer nstimer = NSTimer.CreateScheduledTimer (1, () => {
int i = 0;
while (i < 5000) {
Console.WriteLine (i);
i++;
}
loadingOverlay.Hide ();
});
}
But could someone please explain to me why this is happening, and how to write the code correctly so IOS draws the overlay to screen before it begins the loop.
You're blocking the UI thread, which you should never do. Any long running operation should run in the background thread, even if you want to process it in a synchronous way.
With Xamarin.iOS alpha (supporting async/await), your code would look like this:
public async override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds);
View.Add (loadingOverlay);
await Task.Factory.StartNew (() => {
int i = 0;
while (i < 5000)
{
Console.WriteLine(i);
i++;
}
});
loadingOverlay.Hide ();
}
If you don't have access to async/await, you can achieve the same result with:
Task.Factory.StartNew (() => {
int i = 0;
while (i < 5000)
{
Console.WriteLine(i);
i++;
}
}).ContinueWith(task => InvokeOnMainThread(() => { loadingOverlay.Hide(); }));
but it's less nice (also, check the closing braces, I just typed this code)
Add this Component to your application and be done with it. No need to re-invent the wheel.
I'm calling a powershell script from C#. The script is pretty small and is "gps;$host.SetShouldExit(9)", which list process, and then send back an exit code to be captured by the PSHost object.
The problem I have is when the pipeline has been stopped and disposed, the output reader PSHost collection still seems to be written to, and is filling up. So when I try and copy it to my own output object, it craps out with a OutOfMemoryException when I try to iterate over it. Sometimes it will except with a Collection was modified message. Here is the code.
private void ProcessAndExecuteBlock(ScriptBlock Block)
{
Collection<PSObject> PSCollection = new Collection<PSObject>();
Collection<Object> PSErrorCollection = new Collection<Object>();
Boolean Error = false;
int ExitCode=0;
//Send for exection.
ExecuteScript(Block.Script);
// Process the waithandles.
while (PExecutor.PLine.PipelineStateInfo.State == PipelineState.Running)
{
// Wait for either error or data waithandle.
switch (WaitHandle.WaitAny(PExecutor.Hand))
{
// Data
case 0:
Collection<PSObject> data = PExecutor.PLine.Output.NonBlockingRead();
if (data.Count > 0)
{
for (int cnt = 0; cnt <= (data.Count-1); cnt++)
{
PSCollection.Add(data[cnt]);
}
}
// Check to see if the pipeline has been closed.
if (PExecutor.PLine.Output.EndOfPipeline)
{
// Bring back the exit code.
ExitCode = RHost.ExitCode;
}
break;
case 1:
Collection<object> Errordata = PExecutor.PLine.Error.NonBlockingRead();
if (Errordata.Count > 0)
{
Error = true;
for (int count = 0; count <= (Errordata.Count - 1); count++)
{
PSErrorCollection.Add(Errordata[count]);
}
}
break;
}
}
PExecutor.Stop();
// Create the Execution Return block
ExecutionResults ER = new ExecutionResults(Block.RuleGuid,Block.SubRuleGuid, Block.MessageIdentfier);
ER.ExitCode = ExitCode;
// Add in the data results.
lock (ReadSync)
{
if (PSCollection.Count > 0)
{
ER.DataAdd(PSCollection);
}
}
// Add in the error data if any.
if (Error)
{
if (PSErrorCollection.Count > 0)
{
ER.ErrorAdd(PSErrorCollection);
}
else
{
ER.InError = true;
}
}
// We have finished, so enque the block back.
EnQueueOutput(ER);
}
and this is the PipelineExecutor class which setups the pipeline for execution.
public class PipelineExecutor
{
private Pipeline pipeline;
private WaitHandle[] Handles;
public Pipeline PLine
{
get { return pipeline; }
}
public WaitHandle[] Hand
{
get { return Handles; }
}
public PipelineExecutor(Runspace runSpace, string command)
{
pipeline = runSpace.CreatePipeline(command);
Handles = new WaitHandle[2];
Handles[0] = pipeline.Output.WaitHandle;
Handles[1] = pipeline.Error.WaitHandle;
}
public void Start()
{
if (pipeline.PipelineStateInfo.State == PipelineState.NotStarted)
{
pipeline.Input.Close();
pipeline.InvokeAsync();
}
}
public void Stop()
{
pipeline.StopAsync();
}
}
An this is the DataAdd method, where the exception arises.
public void DataAdd(Collection<PSObject> Data)
{
foreach (PSObject Ps in Data)
{
Data.Add(Ps);
}
}
I put a for loop around the Data.Add, and the Collection filled up with 600k+ so feels like the gps command is still running, but why. Any ideas.
Thanks in advance.
Found the problem. Named the resultant collection and the iterator the same, so as it was iterating, it was adding to the collection, and back into the iterator, and so forth. Doh!.