Your syntax is correct. The DesktopWrapper class of Flashbuilder for Force.com interacts with the local data store directly, which in turns handles the syncing to Force.com. Unfortunately, in the current release, relationship queries are not supported to and from the local store. That's why you see your query "flattened".
Notice in the console window that what is logged is the translation from SOQL to SQL. The SQLl is what is executed against the local store.
If you need to have the data offline, then you will need execute two queries via the app.wrapper class and correlate, or join the data on the client. Since ActionScript is dynamic, you can just attach the Account data to the contact data with the corresponding Account Id.
Here is a stab at what that might look like:
[Bindable]
protected var myData:ArrayCollection = new ArrayCollection();
protected function loginCompleteHandler( event : LoginResultEvent ) : void {
CursorManager.removeBusyCursor();
// When the login is complete the main state should be shown.
currentState = "main";
//Execute the contact Query
app.wrapper.query("Select Id, FirstName, LastName, AccountId From Contact",
new AsyncResponder(contactQueryHandler, faultHandler, myData)
);
}
// This function will iterate over the results creating a string value for the
// "in" clause of the embedded Account query. It also creates a field on the
// Contact dynamic entity so that our data binding works after initially setting
// the data provider variable.
protected function contactQueryHandler(qr:ArrayCollection, token:Object):void {
var acctIdss:String = "";
for each(var contact:DynamicEntity in qr) {
if (contact.AccountId != null && acctIdss.indexOf(contact.AccountId) == -1) {
acctIdss += "'" + contact.AccountId + "',";
}
contact.AccountName = ""; // Add field to contact for account name
myData.addItem(contact); // Add contact to grid data data provider
}
acctIdss = acctIdss.substr(0, acctIdss.length - 1);
// Query for the accounts based on the account ids found in the contact list
app.wrapper.query("Select Id, Name From Account Where Id in (" + acctIdss + ")",
new AsyncResponder(accountQueryHandler, faultHandler));
}
// This function simply iterates over the results and then iterates over the data grid
// data provider to set the Account name for the correct contact. Since data binding has
// already occurred, the account name will be automatically displayed in the grid.
protected function accountQueryHandler(accounts:ArrayCollection, token:Object):void {
for each (var account:DynamicEntity in accounts) {
for each(var contact:DynamicEntity in myData) {
if (contact.AccountId == account.Id) {
contact.AccountName = account.Name;
}
}
}
}
<s:Group includeIn="main"
enabled="{!app.loginPending}"
width="100%"
height="100%">
<s:layout>
<s:BasicLayout/>
</s:layout>
<s:VGroup paddingTop="10"
paddingLeft="10"
paddingRight="10"
paddingBottom="10" width="100%" height="100%">
<mx:DataGrid id="dGrid" dataProvider="{myData}" width="100%" height="100%">
</mx:DataGrid>
</s:VGroup>
<flexforforce:StatusBar left="0"
bottom="0"/>
</s:Group>
If you don't need to have offline capability, you can use your original query with the app.connection object.