So I'm new to Dart and WebStorm and I'm having trouble with the print statements. I have several print statements in my code, but when I run it, none of them appear.
Here's my code:
import 'dart:io';
void main() {
int choice = 0;
String line;
String firstName, lastName, contact;
do {
print("Please choose a number: /n");
print("1 - Add a contact /n");
print("2 - Edit a contact /n");
print("3 - Look for a contact /n");
print("0 - Exit application /n");
line = stdin.readLineSync();
choice = int.parse(line);
switch (choice) {
case 1:
{
print("Enter contact's first name: ");
firstName = stdin.readLineSync();
print("Enter contact's last name: ");
lastName = stdin.readLineSync();
print("Enter a contact number: ");
contact = stdin.readByteSync();
print("Is this an office or home contact?");
print("Is this an office or home contact?");
}
case 0:
{
return 0;
}
}
} while (choice > 3 || choice < 0);
}
The console shows this when I try to run it:
C:\dart-sdk\bin\dart.exe --ignore-unrecognized-flags --checked --enable-vm-service:51901 --trace_service_pause_events "C:\Users\myname\WebstormProjects\untitled\Test.dart"
Process finished with exit code 254
How are attempting to run the dart file? I just created a new Project and pasted your code in and right clicked on file and pressed run "filename".
Then, I recieved this
Please choose a number: /n
1 - Add a contact /n
2 - Edit a contact /n
3 - Look for a contact /n
0 - Exit application /n
Related
I have an EA (i.e Robot) i want people to have it on their PC. is it possible to activate or deactivate it from my PC based on their subscription condition? Explanation: if Mr. A has subscribed for this month, i will be the one to activate it from my PC. Meaning, even though the EA is on their system, they won't be able to activate or deactivate it.
You can use your PC (need to make sure that you have a static IP address and then launch a web application). Simpler is to rent a VPS. Then, create a simple web application, probably using Django (Python) or PHP, with REST webservice and admin panel.
EA side: every robot is compiled, EX4 file is provided to a client. Your client adds http://yourwebsite.org/ into list of allowed urls in MT4 then uses the EA. When EA is attached to the chart, OnInit() function is called, use WebRequest() function inside that block to let your EA contact your website and ask whether it can work (probably client may pass login and password, or account number and broker name (with client name if you wish). The webserver receives that data and makes validation.
Another question is how to let your EA to work till some time. Easiest way is to call the webserver once a day (at random time seems better) with the same validation request. If validation fails - EA stops working.
Finally, think of how you are going to deactivate your EA... It might happen that EA opened plenty of deals and pending orders, and if it kills itself with ExpertRemove(), those deals will remain in MT4. So probably it would be better to notify the client that EA is no longer active, and follow the existing orders, close all at breakeven if possible, or other solutions that depends on your EA logic. The following piece of code worked for some clients without any complaints, you are welcome to use it (with your super admin, password if needed, and domain name).
class CLicenseOnline : public CObject
{
private:
string m_login;
string m_password;
datetime m_nextCheck;
int m_prevResult;
string m_url;
int m_strategyId;
public:
CLicenseOnline(const string login,const string password,const int id):
m_login(login),m_password(password),m_nextCheck(0),m_strategyId(id),m_prevResult(-1)
{
bool isCheckingRequired=false;
if(CLicenseOnline::isSuperAdmin(login,password))
{
printf("%i %s - Hello, SUPER ADMIN!",__LINE__,__FILE__);
isCheckingRequired=true;
}
isCheckingRequired= isCheckingRequired || IsTesting();
if(isCheckingRequired)
{
m_nextCheck=INT_MAX;
m_prevResult=1;
}
else
{
m_url=CLicenseOnline::genUrl(login,password);
}
}
~CLicenseOnline(){}
int check()
{
if(TimeCurrent()>m_nextCheck)
{
int result=this.checkMain();
switch(result)
{
case 1: m_nextCheck=this.generateNextDate(); m_prevResult=1;break;
default:
case 0: m_nextCheck=TimeCurrent()+PeriodSeconds(PERIOD_M1); m_prevResult=0;break;
case-1: m_nextCheck=TimeCurrent()+PeriodSeconds(PERIOD_H1); m_prevResult=-1;break;
}
}
return(m_prevResult);
}
static string genUrl(const string login,const string password)
{
const string http="localhost";
return(StringFormat("http://%s/verify/?Login=%s&&Password=%s&&Check=%d",http,login,password,2147483647));
}
static string getHttpResponce(const string url)
{
char data[],res[];
string cookies=NULL, headers=NULL,result;
ResetLastError();
int answer = WebRequest("GET",url,cookies,NULL,5000,data,0,res,headers);
if(answer==200)
{
result = CharArrayToString(res);
return(result);
}
//printf("%i - result=%d|%s|size=%d; %d",__LINE__,answer,result,ArraySize(res),GetLastError());
return(NULL);
}
private:
static bool isSuperAdmin(const string login,const string password)
{
static string
superAdminLogin="Admin",
superAdminPassword="password";
//ATTENTION! Edit the login and password here!
return login==superAdminLogin && password==superAdminPassword;
}
datetime generateNextDate()const
{
return(iTime(_Symbol,PERIOD_D1,0)+PeriodSeconds(PERIOD_D1)+MathRand()%PeriodSeconds(PERIOD_D1));
}
int checkMain()const
{
string respond=CLicenseOnline::getHttpResponce(m_url);
if(respond==NULL)
return(0);//try later
CJAVal js(NULL,jtUNDEF);
if(!js.Deserialize(respond))
{
printf("%i %s - failed to deserialize %s",__LINE__,__FUNCTION__,respond);
return(-1);
}
int retCode=(int)js["key"].ToInt();
switch(retCode)
{
case -1: Alert("incorrect password");return(0);
case -2: Alert("incorrect key!");return(0);
case -3: Alert("incorrect request method!");return(0);
case -4:
case -5: Alert("no such login");return(0);
default:
Alert(StringFormat("%i %s - incorrect login/password/no such user!",__LINE__,__FUNCTION__));
return(0);
case 200:
{
CJAVal *valueJs=js["value"];
if(!this.checkStatus(valueJs["Status"].ToStr()))
return(-1);
if(!this.checkAccount(
(int)valueJs["Allow_account_1"].ToInt(),(int)valueJs["Allow_account_2"].ToInt(),(int)valueJs["Allow_account_3"].ToInt()))
return -1;
bool strategyX=(bool)valueJs["Allow_strategy_"+(string)m_strategyId].ToBool();
if(!stategyX)
{
return(-1);
}
return(1);
}
}
return(-1);
}
bool checkStatus(const string status)const
{ //printf("%i %s - status = |%s|%d",__LINE__,__FUNCTION__,status,IsDemo());
if(status=="demo")
{
if(!IsDemo())
{
string message=StringFormat("your login %s is allowed to trade on Demo accounts only!",m_login);
Alert(message);
printf("%i %s - %s",__LINE__,__FILE__,message);
return(false);
}
return(true);
}
if(status!="active")
{
string message=StringFormat("status of your login [%s] is [%s] so not allowed to trade!",m_login,status);
Alert(message);
printf("%i %s - %s",__LINE__,__FILE__,message);
return(false);
}
return(true);
}
bool checkAccount(const int acc1,const int acc2,const int acc3)const
{
if(acc1==0 && acc2==0 && acc3==0)
return(true);
int currentAccount=AccountNumber();
if(acc1==currentAccount || acc2==currentAccount || acc3==currentAccount)
return(true);
string message=StringFormat("allowed accounts are only %d, %d and %d, your account %d is not allowed!",acc1,acc2,acc3,currentAccount);
Alert(message);
printf("%i %s - %s",__LINE__,__FUNCTION__,message);
return(false);
}
};
I would like to simulate a keyboard backspace delete event from a string in Flutter (or Dart). Something like:
String str = "helloπ΅π¬δ½ 们πππ¨βπ©βπ¦"
myBackspace(str) // will return "helloπ΅π¬δ½ 们ππ"
myBackspace(str) // will return "helloπ΅π¬δ½ 们π"
myBackspace(str) // will return "helloπ΅π¬δ½ 们"
myBackspace(str) // will return "helloπ΅π¬δ½ "
myBackspace(str) // will return "helloπ΅π¬"
myBackspace(str) // will return "hello"
myBackspace(str) // will return "hell"
Update
Dart team released a helper package that helps achieving this. String.characters.skipLast(1) should be able to do what you expect.
Old answer
First, let's get to some definitions. According to this page:
Bytes: 8-bit. The number of bytes that a Unicode string will take up in memory or storage depends on the encoding.
Code Units: The smallest bit combination that can be used to express a single unit in text encoding. For example 1 code unit in UTF-8 would be 1 byte, 2 bytes in UTF-16, 4 bytes in UTF-32.
Code Points [or rune]: Unicode character. A single integer value (from U+0000-U+10FFFF) on a Unicode space.
Grapheme clusters: A single character perceived by the user. 1 grapheme cluster consists of several code points.
When you remove the last char using substring, you're actually removing the last code unit. If you run print(newStr.codeUnits) and print(str.codeUnits), you'll see that the rune 128512 is equivalent to the joint of the code units 55357 and 56832, so 55357 is actually valid, but doesn't represent anything without the "help" of another code unit.
In fact, you don't want to use substring() when there's non-ASCII chars in your String (like emojis or arabic letters). It'll never work. What you have to do is remove the last grapheme cluster. Something as simple as that:
str.graphemeClusters.removeLast()
However, Dart doesn't support this yet. There are several issues around this point. Some of those:
https://github.com/dart-lang/language/issues/34
https://github.com/dart-lang/language/issues/49
This lack of support seams to result in some other of issues, like the one you mentioned and this one:
https://github.com/flutter/flutter/issues/31818
String formatText(String str) {
final RegExp regExp = RegExp(r'(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])');
if(str.contains(regExp)){
str = str.replaceAll(regExp,'');
}
return str; }
Ex: Go to https://dartpad.dev/ to test:
String str = "ThaiKVεγθ‘γγγ©γγππππ°ππ" => ThaiKVεγθ‘γγγ©γγ
This answer still has problem
Since dart does not provide the data type 'Grapheme Cluster', I try to use method channel to do this using kotlin:
Step 1: Create a new 'Flutter Plugin' project, name the project 'gmc01', 2 files will be created automatically, namely 'gmc01.dart' and 'main.dart'.
Step 2: replace the codes in gmc01.dart with the following:
import 'dart:async';
import 'package:flutter/services.dart';
class Gmc01 {
static const MethodChannel _channel =
const MethodChannel('gmc01');
static Future<String> removeLastGMC(String strOriginal) async {
final String version = await _channel.invokeMethod('removeLastGMC', strOriginal);
return version;
}
}
Step 3: Replace the codes in main.dart with the following:
import 'package:gmc01/gmc01.dart';
void main() async {
String strTemp = '12345678ζ们5π΅π¬δ½ ππ¨βπ©βπ¦';
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
}
Step 4: Inside android/build.gradle, change the minSdkVersion from 16 to 24.
Step 5: Inside example/android/app/build.gradle, change the minSdkVersion from 16 to 24.
Step 6: Click File->Open, select gmc01->android, then click 'OK', the kotlin part of the plugin will be opened (In another Window).
Step 7: Replace the codes in Gmc01Plugin.kt with the following: (Replace the first line with your own package name)
package com.example.gmc01 // replace the left with your own package name
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import android.icu.text.BreakIterator
class Gmc01Plugin: MethodCallHandler {
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), gmc01)
channel.setMethodCallHandler(Gmc01Plugin())
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
var strArg: String
strArg = call.arguments.toString()
var boundary = BreakIterator.getWordInstance()
boundary.setText(strArg);
when (call.method) {
removeLastGMC -> {
result.success(removeLastGMC(boundary, strArg))
}
else -> {
result.notImplemented()
}
}
}
fun removeLastGMC(boundary: BreakIterator, source: String):String {
val end = boundary.last()
val start = boundary.previous()
return source.substring(0, start)
}
}
Step 8: Go back to the window of the plugin, and click 'Run'.
Here are the output in the console:
I/flutter (22855): 12345678ζ们5π΅π¬δ½ π
I/flutter (22855): 12345678ζ们5π΅π¬δ½
I/flutter (22855): 12345678ζ们5π΅π¬
I/flutter (22855): 12345678ζ们5
I/flutter (22855): 12345678ζ们
I/flutter (22855): 12345678
I/flutter (22855):
As you can see, the 'Family Emoji', 'Face Emoji' and 'Country Flag' emoji are removed correctly, but the Chinese 2 chars 'ζ们' and the digits '12345678' are removed by using a single removeLastGMC, so still need to figure out how to distinguish Chinese Double Bytes characters / English Chars / Emojis.
BTW, I don't know how to do the Swift part, can someone help?
Its a bit unclear to what you want to check. I suggest you remove the -1 from the substring because it will break the emoji's code snip
void main() {
var str = "abcπ";
var newStr = str.substring(0, str.length); // i removed it here
print(newStr);
print(newStr.runes);
print(str.runes);
}
This will give the output of
abcπ
(97, 98, 99, 128512)
(97, 98, 99, 128512)
Tested in https://dartpad.dartlang.org/
The code is not working
The code is not working properly. I just put here for reference.
Trial 1
Problem: can not handle π΅π¬ and π¨βπ©βπ¦ properly.
String myBackspace(String str) {
Runes strRunes = str.runes;
str = String.fromCharCodes(strRunes, 0, strRunes.length - 1);
print(str);
return str;
}
Trial 2
Problem: can not handle connected emoji sequence ππ and π¨βπ©βπ¦ properly.
Based on the link
String myBackspace(String str) {
int i = 0;
while (str.length > 0) {
i++;
int removedCharCode = str.codeUnitAt(str.length - 1);
if (isWellFormattedUTF16(removedCharCode)) break;
str = str.substring(0, str.length - 1);
}
if (i == 1) str = str.substring(0, str.length - 1);
print(str);
return str;
}
bool isWellFormattedUTF16(int charCode) {
int surrogateLeadingStart = 0xD800;
int surrogateLeadingEnd = 0xDBFF;
int surrogateTrailingStart = 0xDC00;
int surrogateTrailingEnd = 0xDFFF;
if (!(charCode >= surrogateLeadingStart && charCode <= surrogateLeadingEnd) &&
!(charCode >= surrogateTrailingStart && charCode <= surrogateTrailingEnd)) return true;
return false;
}
if someone need simple solution to remove emojies from string try this.
String str = "helloπ΅π¬δ½ 们πππ¨βπ©βπ¦"Δ°
final RegExp REGEX_EMOJI = RegExp(r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])');
if(str.contains(REGEX_EMOJI)){
str = str.replaceAll(REGEX_EMOJI,'');
}
With RegExp and replaceAll:
final regex = RegExp(
"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-
\udfff]|\ud83e[\ud000-\udfff])");
final textReplace = String.replaceAll(regex, '');
You can do a method like this one
bool isValid(String prevString, String newString){
if (prevString == newString)
return true;
else return false;
}
then in your keyboard you validate with an onChange property
TextField(
onChanged: (text) {
isValid(varYouHad ,text); //validate
},
);
I'm programming a .Net Core (2.1 preview, C# 7.3) Streaming Console App with L2T (5.0.0 beta 2) but even with the strm.TweetMode == TweetMode.Extended the query gives "compat" tweets back, the FullText property is empty.
You can reproduce this with the L2T query below.
I searched online, I've found something similar (with 'search' instead of 'Streaming') but no answers, except to add && strm.TweetMode == TweetMode.Extended, which I did.
Any ideas?
try
{
await
(from strm in twitterCtx.Streaming
.WithCancellation(cancelTokenSrc.Token)
where
strm.Type == StreamingType.Filter
&& strm.Track == "twitter"
&& strm.Language == "nl"
&& strm.TweetMode == TweetMode.Extended
select strm)
.StartAsync(async strm =>
{
await HandleStreamResponse(strm);
if (count++ >= 20)
cancelTokenSrc.Cancel();
});
}
[Update May 30th]
Found something. It's in the subroutine "HandleStreamResponse" (code below). The Status.TweetMode and Status.ExtendedTweet.TweetMode both return "Compat" for all tweets, but the full text of a tweet is in status.ExtendedTweet.FullText
But even with this check, retweets are truncated to 140 chars max. I do not need retweets for my progam so I filter them out.
I do not know, yet, how to filter retweets from a stream directly (is it possible?), so I check the retweetstatus of the Status from the stream result. It's in the code below.
FYI: In the examples of Linq To Twitter for this subroutine Joe Mayo uses the following line of code, but that doesn't work: Console.WriteLine("{0}: {1} {2}", status.StatusID, status.User.ScreenNameResponse, status.Text ?? status.FullText);
Even with && strm.TweetMode == TweetMode.Extended in the L2T query, the status.FullText is empty.
There is more code than neccesary in the example below, but I used it for clarity.
static async Task<int> HandleStreamResponse(StreamContent strm)
{
switch (strm.EntityType)
{
case StreamEntityType.Status:
var status = strm.Entity as Status;
if (status.RetweetedStatus.StatusID == 0)
{
if (status.ExtendedTweet.FullText != null)
{
Console.WriteLine("Longer than 140 - \"#{0}\": {1} (TweetMode:{2})",
status.User.ScreenNameResponse, status.ExtendedTweet.FullText, status.TweetMode);
}
else
{
Console.WriteLine("Shorter than 140 - \"#{0}\": {1} (TweetMode:{2})",
status.User.ScreenNameResponse, status.Text, status.TweetMode);
}
}
// Console.WriteLine("{0}: {1} {2}", status.StatusID, status.User.ScreenNameResponse, status.Text ?? status.FullText);
break;
default:
Console.WriteLine("Unknown - " + strm.Content + "\n");
break;
}
return await Task.FromResult(0);
}
}
Here are my observations:
status.ExtentendedTweet.FullText should hold the tweet in normal circumstances.
However, if the tweet is retweeted, then status.RetweetedStatus.ExtendedTweet.FullText should hold the tweet.
If you can't find the FullText in either of those circumstances, use status.Text.
I'm updating the sample with the following:
case StreamEntityType.Status:
var status = strm.Entity as Status;
string text = null;
if (status.ExtendedTweet?.FullText != null)
text = status.ExtendedTweet?.FullText;
else if (status.RetweetedStatus?.ExtendedTweet?.FullText != null)
text = status.RetweetedStatus?.ExtendedTweet?.FullText;
else
text = status.Text;
Console.WriteLine("Status - #{0}: {1}", status.User.ScreenNameResponse, text);
break;
Note: Via Twitter documentation (see Standard Streams), TweetMode doesn't apply to streams. Additionally, the docs say the ExtentedTweet should always be there with FullText. As we can see, that isn't the full picture in practice. I'll mark Streaming.TweetMode as obsolete in upcoming releases.
I am developing cocos2d-x game which have online game mode.
Online game designed and implemented by Photon Cloud SDK(http://www.exitgames.com).
I implemented only ios version but it doesn't work.
The codes that I have implemented are blow.
void NetworkLogic::opJoinRandomRoom()
{
ExitGames::Common::JVector<ExitGames::LoadBalancing::Room> roomList;
roomList = mLoadBalancingClient.getRoomList();
int count = roomList.getSize();
CCLog("Room Count = %d", count);
if(count == 0)
{
this->opCreateRoom();
}else{
mLoadBalancingClient.opJoinRandomRoom();
}
}
void NetworkLogic::update(float dt)
{
this->run();
}
void NetworkLogic::run(void)
{
if(mLastInput == INPUT_EXIT && mStateAccessor.getState() != STATE_DISCONNECTING && mStateAccessor.getState() != STATE_DISCONNECTED)
{
disconnect();
mStateAccessor.setState(STATE_DISCONNECTING);
}
else
{
State state = mStateAccessor.getState();
switch(state)
{
case STATE_INITIALIZED:
connect();
mStateAccessor.setState(STATE_CONNECTING);
break;
case STATE_CONNECTING:
break; // wait for callback
case STATE_CONNECTED:
{
ExitGames::Common::JVector<ExitGames::LoadBalancing::Room> roomList;
roomList = mLoadBalancingClient.getRoomList();
int count = roomList.getSize();
ExitGames::Common::JString tmp;
tmp = count;
EGLOG(ExitGames::Common::DebugLevel::INFO, tmp);
CCLog("Room count in Room = %d", count);
switch(mLastInput)
{
case INPUT_CREATE_GAME: // create Game
opCreateRoom();
break;
case INPUT_JOIN_RANDOM_GAME: // join Game
opJoinRandomRoom();
mStateAccessor.setState(STATE_JOINING);
break;
default: // no or illegal input -> stay waiting for legal input
break;
}
break;
}
case STATE_JOINING:
break; // wait for callback
case STATE_JOINED:
switch(mLastInput)
{
case INPUT_LEAVE_GAME: // leave Game
mLoadBalancingClient.opLeaveRoom();
mStateAccessor.setState(STATE_LEAVING);
break;
default: // no or illegal input -> stay waiting for legal input
break;
}
break;
case STATE_LEAVING:
break; // wait for callback
case STATE_LEFT:
mStateAccessor.setState(STATE_CONNECTED);
break;
case STATE_DISCONNECTING:
break; // wait for callback
default:
break;
}
}
mLastInput = INPUT_NON;
mLoadBalancingClient.service();
}
First I run one app then getRoomList function returns 0 values.
Also after first room created and run second app but it also returns getRoomList function 0.
Please help me.
I have just taken the code that you have provided in your question and copied it into the according place inside the demo of an otherwise unchanged version 3.2.2.0 build of the Photon C++ Client SDK (and removed the two CCLog() lines to make it compile without cocos2d-x) and it worked just fine for me:
The demo prints 0 for the size of the room list until I let one client create a room. Afterwards the other client prints 1.
Some help with the following would be appreciated. I am writing some console test programs, and I want to be able to enter some parameters from the terminal (I don't want to use command line arguments - too many parameters). I have tried some variations, but I cannot find how to accomplish this. The following is the latest version of my test for terminal input. The problem with this program is that if an error is encountered, the Completer closes automatically, and I want to continue from either the Main() or from fGetNumber() function. While I can see why this program doesn't work, it illustrates what I need to achieve - re-enter the number, but I cannot find how to achieve that. If a valid number is entered, there is no problem. If an invalid number is entered, I cannot find out how to re-enter the number.
The code is as follows, and the problem I have is highlighted by "//////////" :
import "dart:async" as async;
import "dart:io";
void main() {
fGetNumber("Enter Nr of Iterations : ", 0, 999999)
.then((int iIters){
print ("In Main : Iterations selected = ${iIters}");
if (iIters == null) {
print ("In Main: Invalid Number of iterations : ${iIters}.");
} else {
fProcessData(iIters);
}
print ("Main Completed");
});
}
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
int iIters = 0;
async.Completer<int> oCompleter = new async.Completer();
while (!oCompleter.isCompleted) { /////////// This loop does not work ///////
return fGetUserInput(sPrompt).then((String sIters) {
iIters = int.parse(sIters);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
oCompleter.complete(iIters);
return oCompleter.future;
}).catchError((_) => print ("Invalid - number must be from ${iMin} to ${iMax}")
).whenComplete(() => print ("fGetNumber - whenComplete"));// always gets here
}
print ("In fGetNumber (at end of function)"); //// it never gets here
}
async.Future<String> fGetUserInput(String sPrompt) {
print ("In fGetUserInput");
async.Completer<String> oCompleter = new async.Completer();
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
oCompleter.complete("$sData");
oSub.cancel();
});
return oCompleter.future;
}
void fProcessData(int iIters) {
print ("In fProcessData");
for (int iPos = 1; iPos <= iIters; iPos++ ) {
if (iPos%100 == 0) print ("Processed = ${iPos}");
}
print ("In fProcessData - completed ${iIters}");
}
// This loop does not work
Of course it does - you enter it exactly once, where you immediately return and therefore leave the loop and method.
// always gets here
That's because whenComplete() always gets called, on success or on error.
// it never gets here
Because you already returned out of the method.
So what can be done?
The easiest way would be to not rely on fGetUserInput(). Listen to stdin in fGetNumber and only complete the completer / cancel the subscription if the input is valid:
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
async.Completer<String> oCompleter = new async.Completer();
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
try {
int iIters = int.parse(sData);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
oCompleter.complete(iIters);
oSub.cancel();
} catch(e) {
print("Invalid - number must be from ${iMin} to ${iMax}");
stdout.write(sPrompt);
}
});
return oCompleter.future;
}
Are there alternatives?
Of course. There are likely many, many ways to do this. This one for example:
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
async.Completer<int> oCompleter = new async.Completer();
fGetUserInput(sPrompt, oCompleter, (String sIters) {
try {
int iIters = int.parse(sIters);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
return iIters;
} catch(e) {
print ("Invalid - number must be from ${iMin} to ${iMax}");
stdout.write(sPrompt);
}
return null;
});
return oCompleter.future;
}
void fGetUserInput(String sPrompt, async.Completer oCompleter, dynamic inputValidator(String sData)) {
print ("In fGetUserInput");
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
var d = inputValidator(sData);
if(d != null) {
oCompleter.complete(d);
oSub.cancel();
}
});
}
If you really feel there should be something addressed by the Dart team, you could write a feature request. But the Completer is designed to only be completed once. Whatever code you write, you can't just loop to complete it again and again.