Unity WWW webrequest not executing - c#

I have a unity program that changes it content based on the data requested from a webserver. I wrote the following code to do the web-request.
public static string[] GetDBValues(int type, string[] data){
string base_url = "http://localhost/artez/onderzoeks_opdracht/interface_test/get_elements.php";
string typePrefix = "?type=";
string dataPrefix = "data[]=";
string uriString = base_url + typePrefix + type;
foreach (string dataElement in data){
uriString += "&" + dataPrefix + dataElement;
}
Debug.Log("executing url request");
UrlData(uriString);
return new string[] {"a"};
}
public static IEnumerator UrlData(string url){
Debug.Log("searching the web");
using (WWW www = new WWW(url)){
Debug.Log(www.text);
yield return www.text;
}
}
this code compiles and executes but i never see the following Debug.Log("searching the web") being logged. So i believe my code never executes the last part why is this happening?
I am a complete newb to c#.
If something is unclear let me know so i can clarify.

There are many issues in your code:
1.The UrlData function is a coroutine function. You have to use StartCoroutine to call it instead of calling it irectly like a normal function. So, UrlData(uriString); should be StartCoroutine(UrlData(uriString));
2.You have to yield or wait for the WWW request to finish before accessing the WWW.text property. That should be yield return www not yield return www.text.
public static IEnumerator UrlData(string url)
{
Debug.Log("searching the web");
using (WWW www = new WWW(url))
{
yield return www;
Debug.Log(www.text);
}
}
3.It looks like you want GetDBValues to return the result from the UrlData function. If this is true then just add Action as argument to the UrlData function so that you can use that to return the result.
Something like this:
public static IEnumerator UrlData(string url, Action<string> result)
{
Debug.Log("searching the web");
using (WWW www = new WWW(url))
{
yield return www;
if (result != null)
result(www.text);
}
}
then start it like below. The result is in the result variable:
StartCoroutine(UrlData("url", (result) =>
{
Debug.Log(result);
}));

Related

Getting return value of a Coroutine in unity [duplicate]

This question already has answers here:
Unity - need to return value only after coroutine finishes
(3 answers)
Closed 2 years ago.
I'm trying to insert a user to external database in unity.
I need to wait until web server replies with a error or success, but I'm unable to do it....
Here is what I have done until now:
public class DBConnector : MonoBehaviour
{
string BASE_URL = "https://www.XXXXXX.com/API/";
public DBConnector()
{
}
public int registerUser(User user)
{
int returnInt = -1;
StartCoroutine(RegisterUser(user, returnValue =>
{
returnInt = returnValue;
}
));
Debug.Log(returnInt);
return returnInt;
}
IEnumerator RegisterUser(User user, System.Action<int> callback = null)
{
Debug.Log("a register user");
WWWForm form = new WWWForm();
using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
yield return 1;
}
else
{
Debug.Log(www.downloadHandler.text);
yield return 0;
}
}
}
}
and calling it :
Debug.Log("Before registerUser");
conn = FindObjectOfType<DBConnector>();
result = conn.registerUser(user);
Debug.Log("After registerUser");
if (result == 0)
{
SceneManager.LoadScene("AccountCreatedScene");
}
the RegisterUser callback is finished after the registerUser method is finish so always returns -1.
I'm quite newbie on networking thing, so please help.....How to do it?
Thanks
You need to actually call callback with the return value, that's why the delegate you are sending isn't getting called.
IEnumerator RegisterUser(User user, System.Action<int> callback = null)
{
Debug.Log("a register user");
WWWForm form = new WWWForm();
using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
if (callback != null) { callback.Invoke(1); }
}
else
{
Debug.Log(www.downloadHandler.text);
if (callback != null) { callback.Invoke(0); }
}
}
}
And you will need to load the scene inside the delegate you send in; that way it will be called after it's done.
public void Register(User user)
{
StartCoroutine(RegisterUser(user, returnValue =>
{
if (returnValue != 0) {
Debug.LogError("Error");
return;
}
// Place any logic you want post login
SceneManager.LoadScene("AccountCreatedScene");
}
));
}
EDIT: Notice this is pseudo code I wrote in the browser.
Also there is a problem:
int returnInt = -1;
//This is working async so it won't lock your method, just start your Coroutine and go
StartCoroutine(RegisterUser(user, returnValue =>
{
//Inside callback
returnInt = returnValue;
}
));
//go over there where your returnInt is always -1; you should insert that log inside callback
Debug.Log(returnInt);
Long story short you should implement rest of the code inside that lambda
returnValue => { /Here/ };
or else it will be called before your coroutine finishes
I would do this like this:
public void RegisterUser(User user, System.Action<int> callback)
{
StartCoroutine(RegisterUserCoroutine(user, callback);
}
IEnumerator RegisterUserCoroutine(User user, System.Action<int> callback = null)
{
Debug.Log("a register user");
WWWForm form = new WWWForm();
using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
//This is also bad, because default value of your callback is null, so it would throw an error
callback(1);
//This is better
callback?.Invoke(1);
}
else
{
callback(0);
}
}
}
You can't get that int as return value, because you are using async function. So to use that code instead of:
int userId = RegisterUser( user );
DoSomethingWithUserId( userId );
you should write:
RegisterUser( user, DoSomethingWithUserId );

How to Load assetBundle from android local folder?

I'm trying to Load AssetBunddle form Android Folder from in Unity.
but it doesn't work...
I thought URL is wrong.
Changed URL many time, recommended or specification of Unity document but every case failed
Here is My Code.
public string BundleURL;
public int version;
void Start()
{
CleanCache();
BundleURL = "file://"+Application.persistentDataPath + " /BundleTest";
StartCoroutine(LoadAssetBundle(BundleURL));
version = 0;
}
I thought BundleURL is wrong or has problem
IEnumerator LoadAssetBundle(string BundleURL)
{
//BundleURL = "file://" + path ;
GameObject obj;
//ARLog.d ("LoadAssetBundle" + BundleURL);
while (!Caching.ready)
yield return null;
using (WWW www = WWW.LoadFromCacheOrDownload(BundleURL, version))
{
yield return www;
AssetBundle bundle = www.assetBundle;
String[] mPath = bundle.GetAllAssetNames();
String bundleName = null;
if (bundleName != null)
{
AssetBundleRequest request = bundle.LoadAssetAsync(bundleName, typeof(GameObject));
yield return request;
obj = Instantiate(request.asset) as GameObject;
bundle.Unload(false);
www.Dispose();
}
}
}
}
I want Instance Model in my Scene(from BundelTest Folder in Androind)
You have an additional space there in
BundleURL = "file://"+Application.persistentDataPath + " /BundleTest";
in " /BundleTest"!
For paths in general you allways rather should use Path.Combine instead of manually concatenating strings:
BundleURL = Path.Combine(Application.persistentDataPath,"BundleTest");
this makes sure that in the resulting path automatically the correct path separator (/ or \) for the according atrget system is used.
Than note that WWW is obsolete and not that fast &rightarrow; you should have a look at AssetBundle.LoadFromFileAsync there is an example of how to use it
public void IEnumerator LoadBundle()
{
var bundleLoadRequest = AssetBundle.LoadFromFileAsync(Path.Combine(Application.streamingAssetsPath, "BundleTest"));
yield return bundleLoadRequest;
var myLoadedAssetBundle = bundleLoadRequest.assetBundle;
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
yield break;
}
var assetLoadRequest = myLoadedAssetBundle.LoadAssetAsync<GameObject>("MyObject");
yield return assetLoadRequest;
GameObject prefab = assetLoadRequest.asset as GameObject;
Instantiate(prefab);
myLoadedAssetBundle.Unload(false);
}
If you prefer synchronous loading checkout AssetBundle.LoadFromFile instead.
Another note in general: if you are using
using (WWW www = WWW.LoadFromCacheOrDownload(BundleURL, version))
{
....
}
you don't have to use Dispose as it is disposed at the and of the using block automatically.

You are using download over http. Currently Unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon [duplicate]

This question already has an answer here:
You are using download over http. Currently Unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition
(1 answer)
Closed 4 years ago.
I am uploading data to database using WWW.
I have two data classes for data object.
[Serializable]
public class pushdatawrapper{
public string email;
public pushdata[] shotsArray=new pushdata[1];
}
[Serializable]
public class pushdata{
public string timehappened;
public string clubtype;
public pushdata(string timestamp_,string clubtype_)
{
timehappened = timestamp_;
clubtype = clubtype_;
}
}
Loading Json array of data object to database, I tried two approaches.
The first one was ok, but the second one is always failed at calling StartCoroutine function.
First approach
public class BLEBridge : MonoBehaviour {
void Start () {
pushdata_ pdata = new pushdata_("123123123123.0","15","123.0","123.0", "123.0", "123.0","123.0", "123.0","123.0","123.0","123.0","123.0", "First");
pushdatawrapper_ pw = new pushdatawrapper_ ();
pw.email = "test#gmail.com";
pw.shotsArray[0] = pdata;
StartCoroutine (PushData_ (pw));
}
private IEnumerator PushData_(pushdatawrapper_ pdata){
WWW www;
Hashtable postHeader = new Hashtable();
postHeader.Add("Content-Type", "application/json");
string dataToJason = JsonUtility.ToJson(pdata);
Debug.Log ("dataToJason " + dataToJason);
// convert json string to byte
var formData = System.Text.Encoding.UTF8.GetBytes(dataToJason);
www = new WWW("http://rmotion.rapsodo.com/api/push/new", formData, postHeader);
StartCoroutine(WaitForRequest(www));
return www;
}
IEnumerator WaitForRequest(WWW data)
{
yield return data; // Wait until the download is done
if (data.error != null)
{
Debug.Log("There was an error sending request: " + data.text);
}
else
{
Debug.Log("WWW Request: " + data.text);
}
}
}
This first method is always ok.
But in actual implementation, I need to upload data from a static function. Those private IEnumerator PushData_(pushdatawrapper_ pdata) and IEnumerator WaitForRequest(WWW data) can't be static.
So what I did was
I made a separate class as
public class UploadData: MonoBehaviour{
public void uploadindividualshot(pushdatawrapper pw){
StartCoroutine (PushData (pw));
}
private IEnumerator PushData(pushdatawrapper pdata){
WWW www;
Hashtable postHeader = new Hashtable();
postHeader.Add("Content-Type", "application/json");
string dataToJason = JsonUtility.ToJson(pdata);
Debug.Log ("dataToJason " + dataToJason);
// convert json string to byte
var formData = System.Text.Encoding.UTF8.GetBytes(dataToJason);
Debug.Log ("before www ");
www = new WWW("http://rmotion.rapsodo.com/api/push/new", formData, postHeader);
Debug.Log ("after new ");
StartCoroutine(WaitForRequest(www));
Debug.Log ("after WaitForRequest ");
return www;
}
IEnumerator WaitForRequest(WWW data)
{
Debug.Log ("start pushing ");
yield return data; // Wait until the download is done
if (data.error != null)
{
Debug.Log("There was an error sending request: " + data.text);
}
else
{
Debug.Log("WWW Request: " + data.text);
}
}
}
Then call the instance of the class from static method as
public class BLEBridge : MonoBehaviour {
void Start(){}
public unsafe static void GetGolfREsult()
{
pushdata pdata = new pushdata("123123123123.0","15","123.0","123.0", "123.0", "123.0","123.0", "123.0","123.0","123.0","123.0","123.0", "First");
pushdatawrapper pw = new pushdatawrapper();
pw.email = Login.loginName;
pw.shotsArray[0] = pdata;
UploadData up = new UploadData ();
up.uploadindividualshot (pw);
}
}
I always have error as
after new
UploadData:PushData(pushdatawrapper)
BLEBridge:GetGolfREsult()
(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
libc++abi.dylib: terminating with uncaught exception of type Il2CppExceptionWrapper
2018-04-29 17:25:25.565292+0800 RMotion[270:7241] You are using download over http. Currently Unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.
The program has error at StartCoroutine(WaitForRequest(www));
What is wrong with second approach?
In unity calling this line of code automatically start http request
WWW www = new WWW("url");
yield is just the convenience way to tell the coroutine to wait for the process to finish. Don't separate your calls in 2 IEnumerator.
private IEnumerator PushData_(pushdatawrapper_ pdata){
WWW www;
Hashtable postHeader = new Hashtable();
postHeader.Add("Content-Type", "application/json");
string dataToJason = JsonUtility.ToJson(pdata);
Debug.Log ("dataToJason " + dataToJason);
// convert json string to byte
var formData = System.Text.Encoding.UTF8.GetBytes(dataToJason);
www = new WWW("http://rmotion.rapsodo.com/api/push/new", formData, postHeader)
Debug.Log ("start pushing ");
yield return www; // Wait until the download is done
if (www.error != null)
{
Debug.Log("There was an error sending request: " + www.text);
}
else
{
Debug.Log("WWW Request: " + www.text);
}
}
And I don't see any use of pointer in your static method so why did you use the unsafe keyword?

How to wait for a callback before returning

I am trying to port a code from .Net to Unity C# and I am stuck on a syntax including a callback.
Basically, I had to replace the .Net 'HttpClient' library by this one, also called 'HttpClient'. But the 'Get' syntax is not the same and uses a Callback. I am quite new to C# and Http queries and don't know how to deal with this syntax to get the expected return.
The original function written in .Net:
internal static JObject GetToBackOffice(string action)
{
var url = backOfficeUrl;
url = url + action;
HttpClient httpClient = new HttpClient();
var response =
httpClient.GetAsync(url
).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Content.ReadAsStringAsync().Result);
return idProcess;
}
else
return null;
The C# code I am writing for Unity:
internal class Utils
{
internal static JObject GetToBackOffice(string action)
{
var url = backOfficeUrl;
url = url + action;
HttpClient httpClient = new HttpClient();
JObject idProcess = new JObject();
httpClient.GetString(new Uri(url),
(response) =>
{
// Raised when the download completes
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Data);
}
else
{
idProcess = null;
}
});
// Here I would like to wait for the response, so that idProcess is filled with the received data before returning
return idProcess;
}
public class Action
{
public bool SendData(string id, string secretKey, FakeData data)
{
var idProcess = Utils.GetToBackOffice(String.Format("Events/{0}/infos",id));
//...I do then something with idProcess
//Currently, when I use idProcess here, it is still empty since the GetString response hasn't been received yet when this line is executed
return true;
}
}
public class EventHubSimulator : MonoBehaviour
{
void Start()
{
//Fill the parameters (I skip the details)
string oId = ...;
string secretKey = ...;
var vh = ...;
Action action = new Action();
action.SendData(oId, secretKey, vh);
}
}
My issue is that after the GetToBackOffice function, my code directly uses 'idProcess' for something else but this object is empty because the response was not received yet. I would like to wait for the response before my function returns.
I hope I was clear enough. I know that similar question have already been posted but couldn't find a solution to my specific issue.
Edit:
Finally I used a coroutine as Nain suggested but couldn't get what I expected the way he said. This way seems to work (event though it might not be a good way to do it).
public class EventHubSimulator : MonoBehaviour
{
void Start()
{
//Fill the parameters (I skip the details)
string oId = ...;
string secretKey = ...;
var vh = ...;
Utils utils = new Utils();
StartCoroutine(utils.SendData(oId, secretKey, vh));
}
}
public class Utils: MonoBehaviour
{
private const string backOfficeUrl = "http://myurl/api/";
public CI.HttpClient.HttpResponseMessage<string> response;
public IEnumerator SendData(string id, string secretKey, FakeData data)
{
response = null;
yield return GetToBackOffice(String.Format("Events/{0}/infos", id)); //Make a Http Get request
//The next lines are executed once the response has been received
//Do something with response
Foo(response);
}
IEnumerator GetToBackOffice(string action)
{
var url = backOfficeUrl;
url = url + action;
//Make a Http Get request
HttpClient httpClient = new HttpClient();
httpClient.GetString(new Uri(url), (r) =>
{
// Raised when the download completes
if (r.StatusCode == System.Net.HttpStatusCode.OK)
{
//Once the response has been received, write it in the global variable
response = r;
Debug.Log("Response received : " + response);
}
else
{
Debug.Log("ERROR =============================================");
Debug.Log(r.ReasonPhrase);
throw new Exception(r.ReasonPhrase);
}
});
//Wait for the response to be received
yield return WaitForResponse();
Debug.Log("GetToBackOffice coroutine end ");
}
IEnumerator WaitForResponse()
{
Debug.Log("WaitForResponse Coroutine started");
//Wait for response to become be assigned
while (response == null)
{
yield return new WaitForSeconds(0.02f);
}
Debug.Log("WaitForResponse Coroutine ended");
}
}
One solution is polling for completion as Nain submitted as an answer. If you don't want polling you can use a TaskCompletionSource. This Q&A dives a bit deeper into the why and how.
Your code can then be written like this:
async Task CallerMethod()
{
JObject result = await GetToBackOffice(...);
// Do something with result
}
internal static Task<JObject> GetToBackOffice(string action)
{
var tsc = new TaskCompletionSource<JObject>();
var url = backOfficeUrl;
url = url + action;
HttpClient httpClient = new HttpClient();
JObject idProcess = new JObject();
httpClient.GetString(new Uri(url),
(response) =>
{
// Raised when the download completes
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
tsc.SetResult(Newtonsoft.Json.Linq.JObject.Parse(response.Data));
}
else
{
tsc.SetResult(null);
}
});
return tsc.Task;
}
See also the section Async Method Calls a Coroutine And Wait for Completion of this msdn blogpost.
NOTE Tasks, and async/await support is only available as beta functionality in Unity. See also this post.
Write a coroutine like
//Class scope variable is neede to hold Response other wise it will
//be destroied as soon as function is ended
ResponseType response;
IEnumerator WaitForResponce(ResponseType response)
{
this.response = response;
while(this.response.Data == null)
yield return new WaitForSeconds (0.02f);
//do what you want here
}
and call the coroutine
httpClient.GetString(new Uri(url),
(response) =>
{
// Raised when the download completes
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
//idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Data);
StartCoroutine(WaitForResponse(response));
}
else
{
idProcess = null;
}
});
If the GetString method is returning a Task object then you can use the Wait() method to let the Task finished processing before a result is returned.
var task = httpClient.GetString({
impl here..
});
task.Wait();
return idProcess;

passing string from IEnumerator to void

I'm trying to pass a string from an IEnumerator to a void. but the string keeps coming out null :( here is my code: I'm new to this so please help Thanks all.
my Void
public void ResisterButton ()
{
//checked Username
if (Username != "") {
StartCoroutine (CheackDBUN (Username));
Debug.LogWarning("User Data String in ResisterButton " + userDataString); // not work is empty :(
// do stuff with text eg turn bool UN = true
more code not to do with this
and the IEnumerator
IEnumerator CheackDBUN(string UsernamePost ){
WWWForm form = new WWWForm ();
form.AddField ("usernamePost", UsernamePost);
WWW userData = new WWW(checkUsernameURL, form);
yield return userData;
string userDataString = userData.text;
Debug.LogWarning("User Data String in CheackDBUN " + userDataString); //Working has text
yield return (userDataString);
}
the Mysql and php code is all working fine
Thanks hope that works if need to know more please ask
You have to wait for the coroutine to finish before using the userDataString result. Even though you are not changing userDataString in CheackDBUN (which i assume was your intention) you can still probably see that User Data String in ResisterButton is logged before User Data String in CheackDBUN.
You can use a callback delegate to tell the coroutine what to do with userDataString once it's finished.
public void ResisterButton ()
{
if (Username != "")
{
StartCoroutine(CheackDBUN(Username,
(userDataString) =>
{
Debug.LogWarning("User Data String in ResisterButton " + userDataString);
// do stuff with text eg turn bool UN = true
}
));
}
}
IEnumerator CheackDBUN(string UsernamePost, Action<string> callback){
WWWForm form = new WWWForm ();
form.AddField ("usernamePost", UsernamePost);
WWW userData = new WWW(checkUsernameURL, form);
yield return userData;
string userDataString = userData.text;
Debug.LogWarning("User Data String in CheackDBUN " + userDataString); //Working has text
callback(userDataString);
}
You need to make the void function accept a string like so:
public void ResisterButton (string str){
And then pass the user data string to it like this:
Debug.LogWarning("User Data String in CheackDBUN " + userDataString); //Working has text
ResisterButton(userDataString);
yield return (userDataString);

Categories