├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── mobile.yml ├── .gitignore ├── CosmosDbSampleApp.Droid ├── Assets │ └── AboutAssets.txt ├── CosmosDbSampleApp.Droid.csproj ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs └── Resources │ ├── AboutResources.txt │ ├── drawable-hdpi │ └── icon.png │ ├── drawable-xhdpi │ └── icon.png │ ├── drawable-xxhdpi │ ├── Add.png │ └── icon.png │ ├── drawable │ └── icon.png │ ├── layout │ ├── Tabbar.axml │ └── Toolbar.axml │ └── values │ └── styles.xml ├── CosmosDbSampleApp.Shared ├── Constants │ ├── AutomationIdConstants.cs │ └── PageTitles.cs ├── CosmosDbSampleApp.Shared.projitems └── CosmosDbSampleApp.Shared.shproj ├── CosmosDbSampleApp.UITests ├── AppInitializer.cs ├── CosmosDbSampleApp.UITests.csproj ├── Pages │ ├── AddPersonPage.cs │ ├── BasePage.cs │ └── PersonListPage.cs ├── TestConstants.cs └── Tests │ ├── BaseTest.cs │ ├── ReplTests.cs │ └── Tests.cs ├── CosmosDbSampleApp.iOS ├── AppDelegate.cs ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── CosmosDbSampleApp.iOS.csproj ├── CustomRenderers │ ├── AddPersonPageCustomRenderer.cs │ └── EntryClearButtonCustomRenderer.cs ├── Entitlements.plist ├── Info.plist ├── LaunchScreen.storyboard ├── Main.cs └── Resources │ └── Add@2x.png ├── CosmosDbSampleApp.sln ├── CosmosDbSampleApp ├── App.cs ├── Constants │ ├── ColorConstants.cs │ └── DocumentDbConstants.cs ├── CosmosDbSampleApp.csproj ├── Models │ ├── CosmosDbModel.cs │ └── PersonModel.cs ├── Page │ ├── AddPersonPage.cs │ ├── Base │ │ └── BaseContentPage.cs │ └── PersonListPage.cs ├── Services │ ├── DebugService.cs │ └── DocumentDbService.cs ├── ViewModels │ ├── AddPersonViewModel.cs │ ├── Base │ │ └── BaseViewModel.cs │ └── PersonListViewModel.cs └── Views │ ├── AddPerson │ └── AddPersonPageEntry.cs │ └── PersonList │ └── PersonListDataTemplate.cs ├── Directory.Build.props ├── License.md └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [brminnick] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/mobile.yml: -------------------------------------------------------------------------------- 1 | name: Xamarin 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | jobs: 12 | 13 | Build_Android: 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | - name: Restore NuGet 20 | run: | 21 | nuget restore 22 | 23 | - name: Inject API Keys 24 | run : | 25 | AzureConstantsFile=`find . -name DocumentDbConstants.cs | head -1` 26 | echo AzureConstantsFile = $AzureConstantsFile 27 | 28 | sed -i '' "s/#error Missing Keys/\/\/#error Missing Keys/g" "$AzureConstantsFile" 29 | 30 | - name: Build Android App 31 | run: | 32 | msbuild ./CosmosDbSampleApp.Droid/CosmosDbSampleApp.Droid.csproj /verbosity:normal /p:Configuration=Release 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xamarinstudio,visualstudio,visualstudiocode,xcode,android,macos,csharp,f#,fastlane,java,jetbrains,linux,monodevelop,objective-c,swift,sublimetext,unity 3 | 4 | ### fastlane ### 5 | # fastlane - A streamlined workflow tool for Cocoa deployment 6 | 7 | # fastlane specific 8 | fastlane/report.xml 9 | 10 | # deliver temporary files 11 | fastlane/Preview.html 12 | 13 | # snapshot generated screenshots 14 | fastlane/screenshots/**/*.png 15 | fastlane/screenshots/screenshots.html 16 | 17 | # scan temporary files 18 | fastlane/test_output 19 | 20 | 21 | ### XamarinStudio ### 22 | bin/ 23 | obj/ 24 | *.userprefs 25 | 26 | 27 | ### VisualStudioCode ### 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | 34 | 35 | ### Xcode ### 36 | # Xcode 37 | # 38 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 39 | 40 | ## Build generated 41 | build/ 42 | DerivedData/ 43 | 44 | ## Various settings 45 | *.pbxuser 46 | !default.pbxuser 47 | *.mode1v3 48 | !default.mode1v3 49 | *.mode2v3 50 | !default.mode2v3 51 | *.perspectivev3 52 | !default.perspectivev3 53 | xcuserdata/ 54 | 55 | ## Other 56 | *.moved-aside 57 | *.xccheckout 58 | *.xcscmblueprint 59 | 60 | 61 | ### Android ### 62 | # Built application files 63 | *.apk 64 | *.ap_ 65 | 66 | # Files for the ART/Dalvik VM 67 | *.dex 68 | 69 | # Java class files 70 | *.class 71 | 72 | # Generated files 73 | gen/ 74 | out/ 75 | Resource.designer.cs 76 | 77 | # Gradle files 78 | .gradle/ 79 | 80 | # Local configuration file (sdk path, etc) 81 | local.properties 82 | 83 | # Proguard folder generated by Eclipse 84 | proguard/ 85 | 86 | # Log Files 87 | *.log 88 | 89 | # Android Studio Navigation editor temp files 90 | .navigation/ 91 | 92 | # Android Studio captures folder 93 | captures/ 94 | 95 | # Intellij 96 | *.iml 97 | .idea/workspace.xml 98 | .idea/tasks.xml 99 | .idea/libraries 100 | 101 | # Keystore files 102 | *.jks 103 | 104 | # External native build folder generated in Android Studio 2.2 and later 105 | .externalNativeBuild 106 | 107 | ### Android Patch ### 108 | gen-external-apklibs 109 | 110 | 111 | ### macOS ### 112 | *.DS_Store 113 | .AppleDouble 114 | .LSOverride 115 | 116 | # Icon must end with two \r 117 | Icon 118 | # Thumbnails 119 | ._* 120 | # Files that might appear in the root of a volume 121 | .DocumentRevisions-V100 122 | .fseventsd 123 | .Spotlight-V100 124 | .TemporaryItems 125 | .Trashes 126 | .VolumeIcon.icns 127 | .com.apple.timemachine.donotpresent 128 | # Directories potentially created on remote AFP share 129 | .AppleDB 130 | .AppleDesktop 131 | Network Trash Folder 132 | Temporary Items 133 | .apdisk 134 | 135 | 136 | ### Csharp ### 137 | ## Ignore Visual Studio temporary files, build results, and 138 | ## files generated by popular Visual Studio add-ons. 139 | ## 140 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 141 | 142 | # User-specific files 143 | *.suo 144 | *.user 145 | *.userosscache 146 | *.sln.docstates 147 | *.vcxproj.filters 148 | 149 | # User-specific files (MonoDevelop/Xamarin Studio) 150 | 151 | # Build results 152 | [Dd]ebug/ 153 | [Dd]ebugPublic/ 154 | [Rr]elease/ 155 | [Rr]eleases/ 156 | x64/ 157 | x86/ 158 | bld/ 159 | [Bb]in/ 160 | [Oo]bj/ 161 | [Ll]og/ 162 | 163 | # Visual Studio 2015 cache/options directory 164 | .vs/ 165 | # Uncomment if you have tasks that create the project's static files in wwwroot 166 | #wwwroot/ 167 | 168 | # MSTest test Results 169 | [Tt]est[Rr]esult*/ 170 | [Bb]uild[Ll]og.* 171 | 172 | # NUNIT 173 | *.VisualState.xml 174 | TestResult.xml 175 | 176 | # Build Results of an ATL Project 177 | [Dd]ebugPS/ 178 | [Rr]eleasePS/ 179 | dlldata.c 180 | 181 | # .NET Core 182 | project.lock.json 183 | project.fragment.lock.json 184 | artifacts/ 185 | **/Properties/launchSettings.json 186 | 187 | *_i.c 188 | *_p.c 189 | *_i.h 190 | *.ilk 191 | *.meta 192 | *.obj 193 | *.pch 194 | *.pdb 195 | *.pgc 196 | *.pgd 197 | *.rsp 198 | *.sbr 199 | *.tlb 200 | *.tli 201 | *.tlh 202 | *.tmp 203 | *.tmp_proj 204 | *.vspscc 205 | *.vssscc 206 | .builds 207 | *.pidb 208 | *.svclog 209 | *.scc 210 | 211 | # Chutzpah Test files 212 | _Chutzpah* 213 | 214 | # Visual C++ cache files 215 | ipch/ 216 | *.aps 217 | *.ncb 218 | *.opendb 219 | *.opensdf 220 | *.sdf 221 | *.cachefile 222 | *.VC.db 223 | *.VC.VC.opendb 224 | 225 | # Visual Studio profiler 226 | *.psess 227 | *.vsp 228 | *.vspx 229 | *.sap 230 | 231 | # TFS 2012 Local Workspace 232 | $tf/ 233 | 234 | # Guidance Automation Toolkit 235 | *.gpState 236 | 237 | # ReSharper is a .NET coding add-in 238 | _ReSharper*/ 239 | *.[Rr]e[Ss]harper 240 | *.DotSettings.user 241 | 242 | # JustCode is a .NET coding add-in 243 | .JustCode 244 | 245 | # TeamCity is a build add-in 246 | _TeamCity* 247 | 248 | # DotCover is a Code Coverage Tool 249 | *.dotCover 250 | 251 | # Visual Studio code coverage results 252 | *.coverage 253 | *.coveragexml 254 | 255 | # NCrunch 256 | _NCrunch_* 257 | .*crunch*.local.xml 258 | nCrunchTemp_* 259 | 260 | # MightyMoose 261 | *.mm.* 262 | AutoTest.Net/ 263 | 264 | # Web workbench (sass) 265 | .sass-cache/ 266 | 267 | # Installshield output folder 268 | [Ee]xpress/ 269 | 270 | # DocProject is a documentation generator add-in 271 | DocProject/buildhelp/ 272 | DocProject/Help/*.HxT 273 | DocProject/Help/*.HxC 274 | DocProject/Help/*.hhc 275 | DocProject/Help/*.hhk 276 | DocProject/Help/*.hhp 277 | DocProject/Help/Html2 278 | DocProject/Help/html 279 | 280 | # Click-Once directory 281 | publish/ 282 | 283 | # Publish Web Output 284 | *.[Pp]ublish.xml 285 | *.azurePubxml 286 | # TODO: Comment the next line if you want to checkin your web deploy settings 287 | # but database connection strings (with potential passwords) will be unencrypted 288 | *.pubxml 289 | *.publishproj 290 | 291 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 292 | # checkin your Azure Web App publish settings, but sensitive information contained 293 | # in these scripts will be unencrypted 294 | PublishScripts/ 295 | 296 | # NuGet Packages 297 | *.nupkg 298 | # The packages folder can be ignored because of Package Restore 299 | **/packages/* 300 | # except build/, which is used as an MSBuild target. 301 | !**/packages/build/ 302 | # Uncomment if necessary however generally it will be regenerated when needed 303 | #!**/packages/repositories.config 304 | # NuGet v3's project.json files produces more ignoreable files 305 | *.nuget.props 306 | *.nuget.targets 307 | 308 | # Microsoft Azure Build Output 309 | csx/ 310 | *.build.csdef 311 | 312 | # Microsoft Azure Emulator 313 | ecf/ 314 | rcf/ 315 | 316 | # Windows Store app package directories and files 317 | AppPackages/ 318 | BundleArtifacts/ 319 | Package.StoreAssociation.xml 320 | _pkginfo.txt 321 | 322 | # Visual Studio cache files 323 | # files ending in .cache can be ignored 324 | *.[Cc]ache 325 | # but keep track of directories ending in .cache 326 | !*.[Cc]ache/ 327 | 328 | # Others 329 | ClientBin/ 330 | ~$* 331 | *~ 332 | *.dbmdl 333 | *.dbproj.schemaview 334 | *.jfm 335 | *.pfx 336 | *.publishsettings 337 | node_modules/ 338 | orleans.codegen.cs 339 | 340 | # Since there are multiple workflows, uncomment next line to ignore bower_components 341 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 342 | #bower_components/ 343 | 344 | # RIA/Silverlight projects 345 | Generated_Code/ 346 | 347 | # Backup & report files from converting an old project file 348 | # to a newer Visual Studio version. Backup files are not needed, 349 | # because we have git ;-) 350 | _UpgradeReport_Files/ 351 | Backup*/ 352 | UpgradeLog*.XML 353 | UpgradeLog*.htm 354 | 355 | # SQL Server files 356 | *.mdf 357 | *.ldf 358 | 359 | # Business Intelligence projects 360 | *.rdl.data 361 | *.bim.layout 362 | *.bim_*.settings 363 | 364 | # Microsoft Fakes 365 | FakesAssemblies/ 366 | 367 | # GhostDoc plugin setting file 368 | *.GhostDoc.xml 369 | 370 | # Node.js Tools for Visual Studio 371 | .ntvs_analysis.dat 372 | 373 | # Visual Studio 6 build log 374 | *.plg 375 | 376 | # Visual Studio 6 workspace options file 377 | *.opt 378 | 379 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 380 | *.vbw 381 | 382 | # Visual Studio LightSwitch build output 383 | **/*.HTMLClient/GeneratedArtifacts 384 | **/*.DesktopClient/GeneratedArtifacts 385 | **/*.DesktopClient/ModelManifest.xml 386 | **/*.Server/GeneratedArtifacts 387 | **/*.Server/ModelManifest.xml 388 | _Pvt_Extensions 389 | 390 | # Paket dependency manager 391 | .paket/paket.exe 392 | paket-files/ 393 | 394 | # FAKE - F# Make 395 | .fake/ 396 | 397 | # JetBrains Rider 398 | .idea/ 399 | *.sln.iml 400 | 401 | # CodeRush 402 | .cr/ 403 | 404 | # Python Tools for Visual Studio (PTVS) 405 | __pycache__/ 406 | *.pyc 407 | 408 | # Cake - Uncomment if you are using it 409 | # tools/ 410 | 411 | 412 | ### F# ### 413 | lib/debug 414 | lib/release 415 | Debug 416 | obj 417 | bin 418 | *.exe 419 | !.paket/paket.bootstrapper.exe 420 | 421 | 422 | ### SublimeText ### 423 | # cache files for sublime text 424 | *.tmlanguage.cache 425 | *.tmPreferences.cache 426 | *.stTheme.cache 427 | 428 | # workspace files are user-specific 429 | *.sublime-workspace 430 | 431 | # project files should be checked into the repository, unless a significant 432 | # proportion of contributors will probably not be using SublimeText 433 | # *.sublime-project 434 | 435 | # sftp configuration file 436 | sftp-config.json 437 | 438 | # Package control specific files 439 | Package Control.last-run 440 | Package Control.ca-list 441 | Package Control.ca-bundle 442 | Package Control.system-ca-bundle 443 | Package Control.cache/ 444 | Package Control.ca-certs/ 445 | bh_unicode_properties.cache 446 | 447 | # Sublime-github package stores a github token in this file 448 | # https://packagecontrol.io/packages/sublime-github 449 | GitHub.sublime-settings 450 | 451 | 452 | ### Swift ### 453 | # Xcode 454 | # 455 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 456 | 457 | ## Build generated 458 | 459 | ## Various settings 460 | 461 | ## Other 462 | *.xcuserstate 463 | 464 | ## Obj-C/Swift specific 465 | *.hmap 466 | *.ipa 467 | *.dSYM.zip 468 | *.dSYM 469 | 470 | ## Playgrounds 471 | timeline.xctimeline 472 | playground.xcworkspace 473 | 474 | # Swift Package Manager 475 | # 476 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 477 | # Packages/ 478 | .build/ 479 | 480 | # CocoaPods 481 | # 482 | # We recommend against adding the Pods directory to your .gitignore. However 483 | # you should judge for yourself, the pros and cons are mentioned at: 484 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 485 | # 486 | # Pods/ 487 | 488 | # Carthage 489 | # 490 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 491 | # Carthage/Checkouts 492 | 493 | Carthage/Build 494 | 495 | # fastlane 496 | # 497 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 498 | # screenshots whenever they are needed. 499 | # For more information about the recommended setup visit: 500 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 501 | 502 | fastlane/screenshots 503 | 504 | 505 | ### JetBrains ### 506 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 507 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 508 | 509 | # User-specific stuff: 510 | 511 | # Sensitive or high-churn files: 512 | .idea/dataSources/ 513 | .idea/dataSources.ids 514 | .idea/dataSources.xml 515 | .idea/dataSources.local.xml 516 | .idea/sqlDataSources.xml 517 | .idea/dynamic.xml 518 | .idea/uiDesigner.xml 519 | 520 | # Gradle: 521 | .idea/gradle.xml 522 | 523 | # Mongo Explorer plugin: 524 | .idea/mongoSettings.xml 525 | 526 | ## File-based project format: 527 | *.iws 528 | 529 | ## Plugin-specific files: 530 | 531 | # IntelliJ 532 | /out/ 533 | 534 | # mpeltonen/sbt-idea plugin 535 | .idea_modules/ 536 | 537 | # JIRA plugin 538 | atlassian-ide-plugin.xml 539 | 540 | # Crashlytics plugin (for Android Studio and IntelliJ) 541 | com_crashlytics_export_strings.xml 542 | crashlytics.properties 543 | crashlytics-build.properties 544 | fabric.properties 545 | 546 | ### JetBrains Patch ### 547 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 548 | 549 | # *.iml 550 | # modules.xml 551 | # .idea/misc.xml 552 | # *.ipr 553 | 554 | 555 | ### Linux ### 556 | 557 | # temporary files which can be created if a process still has a handle open of a deleted file 558 | .fuse_hidden* 559 | 560 | # KDE directory preferences 561 | .directory 562 | 563 | # Linux trash folder which might appear on any partition or disk 564 | .Trash-* 565 | 566 | # .nfs files are created when an open file is removed but is still being accessed 567 | .nfs* 568 | 569 | 570 | ### MonoDevelop ### 571 | #User Specific 572 | *.usertasks 573 | 574 | #Mono Project Files 575 | *.resources 576 | test-results/ 577 | 578 | 579 | ### Objective-C ### 580 | # Xcode 581 | # 582 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 583 | 584 | ## Build generated 585 | 586 | ## Various settings 587 | 588 | ## Other 589 | 590 | ## Obj-C/Swift specific 591 | 592 | # CocoaPods 593 | # 594 | # We recommend against adding the Pods directory to your .gitignore. However 595 | # you should judge for yourself, the pros and cons are mentioned at: 596 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 597 | # 598 | # Pods/ 599 | 600 | # Carthage 601 | # 602 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 603 | # Carthage/Checkouts 604 | 605 | 606 | # fastlane 607 | # 608 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 609 | # screenshots whenever they are needed. 610 | # For more information about the recommended setup visit: 611 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 612 | 613 | 614 | # Code Injection 615 | # 616 | # After new code Injection tools there's a generated folder /iOSInjectionProject 617 | # https://github.com/johnno1962/injectionforxcode 618 | 619 | iOSInjectionProject/ 620 | 621 | ### Objective-C Patch ### 622 | 623 | 624 | ### Unity ### 625 | /[Ll]ibrary/ 626 | /[Tt]emp/ 627 | /[Oo]bj/ 628 | /[Bb]uild/ 629 | /[Bb]uilds/ 630 | /Assets/AssetStoreTools* 631 | 632 | # Autogenerated VS/MD/Consulo solution and project files 633 | ExportedObj/ 634 | .consulo/*.csproj 635 | .consulo/*.unityproj 636 | .consulo/*.sln 637 | .consulo/*.booproj 638 | .consulo/*.svd 639 | 640 | 641 | # Unity3D generated meta files 642 | *.pidb.meta 643 | 644 | # Unity3D Generated File On Crash Reports 645 | sysinfo.txt 646 | 647 | # Builds 648 | *.unitypackage 649 | 650 | 651 | ### Java ### 652 | 653 | # BlueJ files 654 | *.ctxt 655 | 656 | # Mobile Tools for Java (J2ME) 657 | .mtj.tmp/ 658 | 659 | # Package Files # 660 | *.jar 661 | *.war 662 | *.ear 663 | 664 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 665 | hs_err_pid* 666 | 667 | 668 | ### VisualStudio ### 669 | ## Ignore Visual Studio temporary files, build results, and 670 | ## files generated by popular Visual Studio add-ons. 671 | ## 672 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 673 | 674 | # User-specific files 675 | 676 | # User-specific files (MonoDevelop/Xamarin Studio) 677 | 678 | # Build results 679 | 680 | # Visual Studio 2015 cache/options directory 681 | # Uncomment if you have tasks that create the project's static files in wwwroot 682 | #wwwroot/ 683 | 684 | # MSTest test Results 685 | 686 | # NUNIT 687 | 688 | # Build Results of an ATL Project 689 | 690 | # .NET Core 691 | 692 | 693 | # Chutzpah Test files 694 | 695 | # Visual C++ cache files 696 | 697 | # Visual Studio profiler 698 | 699 | # TFS 2012 Local Workspace 700 | 701 | # Guidance Automation Toolkit 702 | 703 | # ReSharper is a .NET coding add-in 704 | 705 | # JustCode is a .NET coding add-in 706 | 707 | # TeamCity is a build add-in 708 | 709 | # DotCover is a Code Coverage Tool 710 | 711 | # Visual Studio code coverage results 712 | 713 | # NCrunch 714 | 715 | # MightyMoose 716 | 717 | # Web workbench (sass) 718 | 719 | # Installshield output folder 720 | 721 | # DocProject is a documentation generator add-in 722 | 723 | # Click-Once directory 724 | 725 | # Publish Web Output 726 | # TODO: Comment the next line if you want to checkin your web deploy settings 727 | # but database connection strings (with potential passwords) will be unencrypted 728 | 729 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 730 | # checkin your Azure Web App publish settings, but sensitive information contained 731 | # in these scripts will be unencrypted 732 | 733 | # NuGet Packages 734 | # The packages folder can be ignored because of Package Restore 735 | # except build/, which is used as an MSBuild target. 736 | # Uncomment if necessary however generally it will be regenerated when needed 737 | #!**/packages/repositories.config 738 | # NuGet v3's project.json files produces more ignoreable files 739 | 740 | # Microsoft Azure Build Output 741 | 742 | # Microsoft Azure Emulator 743 | 744 | # Windows Store app package directories and files 745 | 746 | # Visual Studio cache files 747 | # files ending in .cache can be ignored 748 | # but keep track of directories ending in .cache 749 | 750 | # Others 751 | 752 | # Since there are multiple workflows, uncomment next line to ignore bower_components 753 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 754 | #bower_components/ 755 | 756 | # RIA/Silverlight projects 757 | 758 | # Backup & report files from converting an old project file 759 | # to a newer Visual Studio version. Backup files are not needed, 760 | # because we have git ;-) 761 | 762 | # SQL Server files 763 | 764 | # Business Intelligence projects 765 | 766 | # Microsoft Fakes 767 | 768 | # GhostDoc plugin setting file 769 | 770 | # Node.js Tools for Visual Studio 771 | 772 | # Visual Studio 6 build log 773 | 774 | # Visual Studio 6 workspace options file 775 | 776 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 777 | 778 | # Visual Studio LightSwitch build output 779 | 780 | # Paket dependency manager 781 | 782 | # FAKE - F# Make 783 | 784 | # JetBrains Rider 785 | 786 | # CodeRush 787 | 788 | # Python Tools for Visual Studio (PTVS) 789 | 790 | # Cake - Uncomment if you are using it 791 | # tools/ 792 | 793 | ### VisualStudio Patch ### 794 | 795 | # End of https://www.gitignore.io/api/xamarinstudio,visualstudio,visualstudiocode,xcode,android,macos,csharp,f#,fastlane,java,jetbrains,linux,monodevelop,objective-c,swift,sublimetext,unity 796 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/CosmosDbSampleApp.Droid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4} 7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Library 9 | CosmosDbSampleApp.Droid 10 | CosmosDbSampleApp.Droid 11 | True 12 | Resources\Resource.designer.cs 13 | Resource 14 | Properties\AndroidManifest.xml 15 | Resources 16 | Assets 17 | v11.0 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug 24 | DEBUG; 25 | prompt 26 | 4 27 | Xamarin.Android.Net.AndroidClientHandler 28 | arm64-v8a;armeabi-v7a;x86;x86_64 29 | true 30 | true 31 | d8 32 | 8.0 33 | 34 | 35 | true 36 | pdbonly 37 | true 38 | bin\Release 39 | prompt 40 | 4 41 | true 42 | false 43 | Xamarin.Android.Net.AndroidClientHandler 44 | true 45 | true 46 | true 47 | true 48 | true 49 | true 50 | d8 51 | r8 52 | armeabi-v7a;x86;arm64-v8a;x86_64 53 | 8.0 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | {8B5DFD00-56B2-4D23-8548-900514AE289E} 93 | CosmosDbSampleApp 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | 5 | namespace CosmosDbSampleApp.Droid 6 | { 7 | [Activity(Label = "CosmosDbSampleApp.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] 8 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 9 | { 10 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) 11 | { 12 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); 13 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults); 14 | } 15 | 16 | protected override void OnCreate(Bundle savedInstanceState) 17 | { 18 | TabLayoutResource = Resource.Layout.Tabbar; 19 | ToolbarResource = Resource.Layout.Toolbar; 20 | 21 | base.OnCreate(savedInstanceState); 22 | 23 | Xamarin.Essentials.Platform.Init(this, savedInstanceState); 24 | global::Xamarin.Forms.Forms.Init(this, savedInstanceState); 25 | 26 | LoadApplication(new App()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Android.App; 4 | 5 | // Information about this assembly is defined by the following attributes. 6 | // Change them to the values specific to your project. 7 | 8 | [assembly: AssemblyTitle("CosmosDbSampleApp.Droid")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion("1.0.0")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.axml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable/ 12 | icon.png 13 | 14 | layout/ 15 | main.axml 16 | 17 | values/ 18 | strings.xml 19 | 20 | In order to get the build system to recognize Android resources, set the build action to 21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 22 | instead operate on resource IDs. When you compile an Android application that uses resources, 23 | the build system will package the resources for distribution and generate a class called "R" 24 | (this is an Android convention) that contains the tokens for each one of the resources 25 | included. For example, for the above Resources layout, this is what the R class would expose: 26 | 27 | public class R { 28 | public class drawable { 29 | public const int icon = 0x123; 30 | } 31 | 32 | public class layout { 33 | public const int main = 0x456; 34 | } 35 | 36 | public class strings { 37 | public const int first_string = 0xabc; 38 | public const int second_string = 0xbcd; 39 | } 40 | } 41 | 42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main 43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first 44 | string in the dictionary file values/strings.xml. 45 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.Droid/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.Droid/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/drawable-xxhdpi/Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.Droid/Resources/drawable-xxhdpi/Add.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.Droid/Resources/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/layout/Tabbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 13 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/layout/Toolbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 10 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5 | 6 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Shared/Constants/AutomationIdConstants.cs: -------------------------------------------------------------------------------- 1 | namespace CosmosDbSampleApp.Shared 2 | { 3 | public static class AutomationIdConstants 4 | { 5 | public const string AddPersonPage_SaveButton = nameof(AddPersonPage_SaveButton); 6 | public const string AddPersonPage_CancelButton = nameof(AddPersonPage_CancelButton); 7 | public const string AddPersonPage_AgeEntry = nameof(AddPersonPage_AgeEntry); 8 | public const string AddPersonPage_NameEntry = nameof(AddPersonPage_NameEntry); 9 | public const string AddPersonPage_ActivityIndicator = nameof(AddPersonPage_ActivityIndicator); 10 | 11 | public const string PersonListPage_PersonList = nameof(PersonListPage_PersonList); 12 | public const string PersonListPage_ActivityIndicator = nameof(PersonListPage_ActivityIndicator); 13 | public const string PersonListPage_AddButton = nameof(PersonListPage_AddButton); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Shared/Constants/PageTitles.cs: -------------------------------------------------------------------------------- 1 | namespace CosmosDbSampleApp.Shared 2 | { 3 | public static class PageTitles 4 | { 5 | public const string AddPersonPage = "Add Person"; 6 | public const string PersonListPage = "Person List"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Shared/CosmosDbSampleApp.Shared.projitems: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | {2E0B1C2E-14FE-42FF-9A68-1E7F9648D035} 7 | 8 | 9 | CosmosDbSampleApp.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.Shared/CosmosDbSampleApp.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {2E0B1C2E-14FE-42FF-9A68-1E7F9648D035} 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/AppInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Xamarin.UITest; 4 | 5 | namespace CosmosDbSampleApp.UITests 6 | { 7 | public static class AppInitializer 8 | { 9 | public static IApp StartApp(Platform platform) => platform switch 10 | { 11 | Platform.Android => ConfigureApp.Android.StartApp(), 12 | Platform.iOS => ConfigureApp.iOS.StartApp(), 13 | _ => throw new NotSupportedException() 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/CosmosDbSampleApp.UITests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6} 7 | Library 8 | CosmosDbSampleApp.UITests 9 | CosmosDbSampleApp.UITests 10 | v4.8 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | 21 | 22 | true 23 | bin\Release 24 | prompt 25 | 4 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4} 48 | CosmosDbSampleApp.Droid 49 | False 50 | False 51 | 52 | 53 | {0D33938C-EB02-437F-A395-6BA569C7F6A1} 54 | CosmosDbSampleApp.iOS 55 | False 56 | False 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Pages/AddPersonPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CosmosDbSampleApp.Shared; 3 | using Xamarin.UITest; 4 | using Xamarin.UITest.Android; 5 | using Xamarin.UITest.iOS; 6 | using Query = System.Func; 7 | 8 | namespace CosmosDbSampleApp.UITests 9 | { 10 | public class AddPersonPage : BasePage 11 | { 12 | readonly Query _saveButton, _cancelButton, _ageEntry, _nameEntry, _activityIndicator; 13 | 14 | public AddPersonPage(IApp app, string pageTitle) : base(app, pageTitle) 15 | { 16 | _saveButton = x => x.Marked(AutomationIdConstants.AddPersonPage_SaveButton); 17 | _cancelButton = x => x.Marked(AutomationIdConstants.AddPersonPage_CancelButton); 18 | _ageEntry = x => x.Marked(AutomationIdConstants.AddPersonPage_AgeEntry); 19 | _nameEntry = x => x.Marked(AutomationIdConstants.AddPersonPage_NameEntry); 20 | _activityIndicator = x => x.Marked(AutomationIdConstants.AddPersonPage_ActivityIndicator); 21 | } 22 | 23 | public void TapSaveButton() 24 | { 25 | switch (App) 26 | { 27 | case iOSApp iosApp: 28 | iosApp.Tap(_saveButton); 29 | break; 30 | case AndroidApp androidApp: 31 | androidApp.Tap(x => x.Marked("Save")); 32 | break; 33 | default: 34 | throw new NotSupportedException(); 35 | } 36 | 37 | App.Screenshot("Save Button Tapped"); 38 | } 39 | 40 | public void TapCancelButton() 41 | { 42 | switch (App) 43 | { 44 | case iOSApp iosApp: 45 | iosApp.Tap(_cancelButton); 46 | break; 47 | case AndroidApp androidApp: 48 | androidApp.Tap(x => x.Marked("Cancel")); 49 | break; 50 | default: 51 | throw new NotSupportedException(); 52 | } 53 | 54 | App.Screenshot("Cancel Button Tapped"); 55 | } 56 | 57 | public void EnterAge(int age) 58 | { 59 | App.EnterText(_ageEntry, age.ToString()); 60 | App.DismissKeyboard(); 61 | App.Screenshot($"Entered Age, {age}"); 62 | } 63 | 64 | public void EnterName(string name) 65 | { 66 | App.EnterText(_nameEntry, name); 67 | App.DismissKeyboard(); 68 | App.Screenshot($"Entered Name, {name}"); 69 | } 70 | 71 | public void WaitForActivityIndicator() 72 | { 73 | App.WaitForElement(_activityIndicator); 74 | App.Screenshot("Activity Indicator Appeared"); 75 | } 76 | 77 | public void WaitForNoActivityIndicator() 78 | { 79 | App.WaitForNoElement(_activityIndicator); 80 | App.Screenshot("Activity Indicator Disappeared"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Pages/BasePage.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.UITest; 2 | using System.Threading.Tasks; 3 | 4 | namespace CosmosDbSampleApp.UITests 5 | { 6 | public abstract class BasePage 7 | { 8 | protected BasePage(in IApp app, in string pageTitle) 9 | { 10 | App = app; 11 | Title = pageTitle; 12 | } 13 | 14 | public string Title { get; } 15 | protected IApp App { get; } 16 | 17 | public virtual Task WaitForPageToLoad() => Task.FromResult(App.WaitForElement(Title)); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Pages/PersonListPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using CosmosDbSampleApp.Shared; 5 | using Xamarin.UITest; 6 | using Xamarin.UITest.Android; 7 | using Xamarin.UITest.iOS; 8 | using Query = System.Func; 9 | 10 | namespace CosmosDbSampleApp.UITests 11 | { 12 | public class PersonListPage : BasePage 13 | { 14 | readonly Query _personList, _activityIndicator, _addButton; 15 | 16 | public PersonListPage(IApp app, string pageTitle) : base(app, pageTitle) 17 | { 18 | _personList = x => x.Marked(AutomationIdConstants.PersonListPage_PersonList); 19 | _activityIndicator = x => x.Marked(AutomationIdConstants.PersonListPage_ActivityIndicator); 20 | _addButton = x => x.Marked(AutomationIdConstants.PersonListPage_AddButton); 21 | } 22 | 23 | public bool IsRefreshIndicatorDisplayed => GetIsRefreshIndicatorDisplayed(); 24 | 25 | public override async Task WaitForPageToLoad() 26 | { 27 | await base.WaitForPageToLoad().ConfigureAwait(false); 28 | 29 | WaitForNoActivityIndicator(); 30 | await WaitForPullToRefreshIndicatorToDisappear().ConfigureAwait(false); 31 | } 32 | 33 | public void TapAddButton() 34 | { 35 | switch (App) 36 | { 37 | case iOSApp iosApp: 38 | iosApp.Tap(_addButton); 39 | break; 40 | case AndroidApp androidApp: 41 | androidApp.Tap(x => x.Class("ActionMenuItemView")); 42 | break; 43 | default: 44 | throw new NotSupportedException(); 45 | } 46 | 47 | App.Screenshot("Add Button Tapped"); 48 | } 49 | 50 | public void WaitForActivityIndicator() 51 | { 52 | App.WaitForElement(_activityIndicator); 53 | App.Screenshot("Activity Indicator Appeared"); 54 | } 55 | 56 | public void WaitForNoActivityIndicator(int timeoutInSeconds = 60) 57 | { 58 | App.WaitForNoElement(_activityIndicator, timeout: TimeSpan.FromSeconds(timeoutInSeconds)); 59 | App.Screenshot("Activity Indicator Disappeared"); 60 | } 61 | 62 | public async Task DeletePerson(string name) 63 | { 64 | const string deleteText = "Delete"; 65 | 66 | await ShowContextActions(name).ConfigureAwait(false); 67 | 68 | try 69 | { 70 | App.Tap(deleteText); 71 | } 72 | catch 73 | { 74 | App.Tap(deleteText.ToUpper()); 75 | } 76 | 77 | await Task.Delay(1000).ConfigureAwait(false); 78 | 79 | App.Screenshot($"{name} Deleted"); 80 | } 81 | 82 | public bool DoesContactExist(string name) 83 | { 84 | try 85 | { 86 | App.ScrollDownTo(name); 87 | return true; 88 | } 89 | catch 90 | { 91 | return false; 92 | } 93 | } 94 | 95 | public async Task WaitForPullToRefreshIndicatorToDisappear(int timeoutInSeconds = 60) 96 | { 97 | int counter = 0; 98 | 99 | while (IsRefreshIndicatorDisplayed) 100 | { 101 | await Task.Delay(1000).ConfigureAwait(false); 102 | counter++; 103 | 104 | if (counter >= timeoutInSeconds) 105 | throw new Exception($"Loading the list took longer than {timeoutInSeconds}"); 106 | } 107 | } 108 | 109 | async Task ShowContextActions(string name, int timeoutInSeconds = 180) 110 | { 111 | ScrollToQuery(name, timeoutInSeconds); 112 | 113 | App.Screenshot($"Scrolled to {name}"); 114 | 115 | await Task.Delay(500).ConfigureAwait(false); 116 | 117 | var viewCellQuery = App.Query(name).First(); 118 | var viewCellCenterY = viewCellQuery.Rect.CenterY; 119 | 120 | var displayQuery = App.Query().First(); 121 | var displayQueryWidth = displayQuery.Rect.Width; 122 | 123 | switch (App) 124 | { 125 | case iOSApp iosApp: 126 | iosApp.DragCoordinates(displayQueryWidth - 50, viewCellCenterY, 50, viewCellCenterY); 127 | break; 128 | 129 | case AndroidApp androidApp: 130 | androidApp.TouchAndHold(name); 131 | break; 132 | 133 | default: 134 | throw new NotSupportedException(); 135 | } 136 | 137 | App.Screenshot($"Showed Context Actions for {name}"); 138 | } 139 | 140 | void ScrollToQuery(string text, int timeoutInSeconds = 10) => ScrollToQuery(x => x.Marked(text), timeoutInSeconds); 141 | 142 | void ScrollToQuery(Query query, int timeoutInSeconds = 10) => App.ScrollDownTo(query, timeout: TimeSpan.FromSeconds(timeoutInSeconds)); 143 | 144 | bool GetIsRefreshIndicatorDisplayed() => App switch 145 | { 146 | AndroidApp androidApp => (bool)androidApp.Query(x => x.Class("ListViewRenderer_SwipeRefreshLayoutWithFixedNestedScrolling").Invoke("isRefreshing")).First(), 147 | iOSApp iosApp => App.Query(x => x.Class("UIRefreshControl"))?.Any() ?? false, 148 | _ => throw new NotSupportedException("Platform Not Supported"), 149 | }; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/TestConstants.cs: -------------------------------------------------------------------------------- 1 | namespace CosmosDbSampleApp.UITests 2 | { 3 | public static class TestConstants 4 | { 5 | public const string TestContactName = "Test Contact"; 6 | public const int TestContactAge = 31; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Tests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CosmosDbSampleApp.Shared; 4 | using NUnit.Framework; 5 | using Xamarin.UITest; 6 | 7 | namespace CosmosDbSampleApp.UITests 8 | { 9 | [TestFixture(Platform.Android)] 10 | [TestFixture(Platform.iOS)] 11 | public abstract class BaseTest 12 | { 13 | readonly Platform _platform; 14 | 15 | IApp? _app; 16 | PersonListPage? _personListPage; 17 | AddPersonPage? _addPersonPage; 18 | 19 | protected BaseTest(Platform platform) => _platform = platform; 20 | 21 | protected IApp App => _app ?? throw new NullReferenceException(); 22 | protected PersonListPage PersonListPage => _personListPage ?? throw new NullReferenceException(); 23 | protected AddPersonPage AddPersonPage => _addPersonPage ?? throw new NullReferenceException(); 24 | 25 | [SetUp] 26 | public async Task TestSetup() 27 | { 28 | _app = AppInitializer.StartApp(_platform); 29 | 30 | _personListPage = new PersonListPage(App, PageTitles.PersonListPage); 31 | _addPersonPage = new AddPersonPage(App, PageTitles.AddPersonPage); 32 | 33 | await PersonListPage.WaitForPageToLoad().ConfigureAwait(false); 34 | App.Screenshot("App Launched"); 35 | } 36 | 37 | [TearDown] 38 | public async Task TestTearDown() 39 | { 40 | try 41 | { 42 | await PersonListPage.WaitForPageToLoad().ConfigureAwait(false); 43 | } 44 | catch 45 | { 46 | AddPersonPage.TapCancelButton(); 47 | } 48 | 49 | if (PersonListPage.DoesContactExist(TestConstants.TestContactName)) 50 | { 51 | await PersonListPage.DeletePerson(TestConstants.TestContactName).ConfigureAwait(false); 52 | 53 | PersonListPage.WaitForNoActivityIndicator(); 54 | await PersonListPage.WaitForPullToRefreshIndicatorToDisappear().ConfigureAwait(false); 55 | } 56 | } 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Tests/ReplTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | using Xamarin.UITest; 4 | 5 | namespace CosmosDbSampleApp.UITests 6 | { 7 | public class ReplTests : BaseTest 8 | { 9 | public ReplTests(Platform platform) : base(platform) 10 | { 11 | } 12 | 13 | [Test, Ignore("REPL only used for manually exploring app")] 14 | public void ReplTest() => App?.Repl(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.UITests/Tests/Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | using NUnit.Framework; 4 | 5 | using Xamarin.UITest; 6 | 7 | namespace CosmosDbSampleApp.UITests 8 | { 9 | public class Tests : BaseTest 10 | { 11 | public Tests(Platform platform) : base(platform) 12 | { 13 | } 14 | 15 | [Test] 16 | public async Task AddNewContact() 17 | { 18 | //Arrange 19 | 20 | //Act 21 | PersonListPage.TapAddButton(); 22 | 23 | AddPersonPage.EnterName(TestConstants.TestContactName); 24 | AddPersonPage.EnterAge(TestConstants.TestContactAge); 25 | AddPersonPage.TapSaveButton(); 26 | 27 | await PersonListPage.WaitForPageToLoad().ConfigureAwait(false); 28 | 29 | //Assert 30 | Assert.IsTrue(PersonListPage.DoesContactExist(TestConstants.TestContactName)); 31 | } 32 | 33 | [Test] 34 | public async Task CancelAddNewContact() 35 | { 36 | //Arrange 37 | 38 | //Act 39 | PersonListPage.TapAddButton(); 40 | 41 | AddPersonPage.EnterName(TestConstants.TestContactName); 42 | AddPersonPage.EnterAge(TestConstants.TestContactAge); 43 | AddPersonPage.TapCancelButton(); 44 | 45 | await PersonListPage.WaitForPageToLoad().ConfigureAwait(false); 46 | 47 | //Assert 48 | Assert.IsFalse(PersonListPage.DoesContactExist(TestConstants.TestContactName)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | using Foundation; 3 | 4 | namespace CosmosDbSampleApp.iOS 5 | { 6 | [Register(nameof(AppDelegate))] 7 | public class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 8 | { 9 | public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions) 10 | { 11 | global::Xamarin.Forms.Forms.Init(); 12 | 13 | #if DEBUG 14 | Xamarin.Calabash.Start(); 15 | #endif 16 | 17 | LoadApplication(new App()); 18 | 19 | return base.FinishedLaunching(uiApplication, launchOptions); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "iphone", 5 | "size": "29x29", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "iphone", 10 | "size": "29x29", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "iphone", 15 | "size": "29x29", 16 | "scale": "3x" 17 | }, 18 | { 19 | "idiom": "iphone", 20 | "size": "40x40", 21 | "scale": "2x" 22 | }, 23 | { 24 | "idiom": "iphone", 25 | "size": "40x40", 26 | "scale": "3x" 27 | }, 28 | { 29 | "idiom": "iphone", 30 | "size": "57x57", 31 | "scale": "1x" 32 | }, 33 | { 34 | "idiom": "iphone", 35 | "size": "57x57", 36 | "scale": "2x" 37 | }, 38 | { 39 | "idiom": "iphone", 40 | "size": "60x60", 41 | "scale": "2x" 42 | }, 43 | { 44 | "idiom": "iphone", 45 | "size": "60x60", 46 | "scale": "3x" 47 | }, 48 | { 49 | "idiom": "ipad", 50 | "size": "29x29", 51 | "scale": "1x" 52 | }, 53 | { 54 | "idiom": "ipad", 55 | "size": "29x29", 56 | "scale": "2x" 57 | }, 58 | { 59 | "idiom": "ipad", 60 | "size": "40x40", 61 | "scale": "1x" 62 | }, 63 | { 64 | "idiom": "ipad", 65 | "size": "40x40", 66 | "scale": "2x" 67 | }, 68 | { 69 | "idiom": "ipad", 70 | "size": "50x50", 71 | "scale": "1x" 72 | }, 73 | { 74 | "idiom": "ipad", 75 | "size": "50x50", 76 | "scale": "2x" 77 | }, 78 | { 79 | "idiom": "ipad", 80 | "size": "72x72", 81 | "scale": "1x" 82 | }, 83 | { 84 | "idiom": "ipad", 85 | "size": "72x72", 86 | "scale": "2x" 87 | }, 88 | { 89 | "idiom": "ipad", 90 | "size": "76x76", 91 | "scale": "1x" 92 | }, 93 | { 94 | "idiom": "ipad", 95 | "size": "76x76", 96 | "scale": "2x" 97 | }, 98 | { 99 | "size": "24x24", 100 | "idiom": "watch", 101 | "scale": "2x", 102 | "role": "notificationCenter", 103 | "subtype": "38mm" 104 | }, 105 | { 106 | "size": "27.5x27.5", 107 | "idiom": "watch", 108 | "scale": "2x", 109 | "role": "notificationCenter", 110 | "subtype": "42mm" 111 | }, 112 | { 113 | "size": "29x29", 114 | "idiom": "watch", 115 | "role": "companionSettings", 116 | "scale": "2x" 117 | }, 118 | { 119 | "size": "29x29", 120 | "idiom": "watch", 121 | "role": "companionSettings", 122 | "scale": "3x" 123 | }, 124 | { 125 | "size": "40x40", 126 | "idiom": "watch", 127 | "scale": "2x", 128 | "role": "appLauncher", 129 | "subtype": "38mm" 130 | }, 131 | { 132 | "size": "44x44", 133 | "idiom": "watch", 134 | "scale": "2x", 135 | "role": "longLook", 136 | "subtype": "42mm" 137 | }, 138 | { 139 | "size": "86x86", 140 | "idiom": "watch", 141 | "scale": "2x", 142 | "role": "quickLook", 143 | "subtype": "38mm" 144 | }, 145 | { 146 | "size": "98x98", 147 | "idiom": "watch", 148 | "scale": "2x", 149 | "role": "quickLook", 150 | "subtype": "42mm" 151 | } 152 | ], 153 | "info": { 154 | "version": 1, 155 | "author": "xcode" 156 | } 157 | } -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/CosmosDbSampleApp.iOS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | {0D33938C-EB02-437F-A395-6BA569C7F6A1} 7 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Exe 9 | CosmosDbSampleApp.iOS 10 | CosmosDbSampleApp.iOS 11 | Resources 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\iPhoneSimulator\Debug 18 | DEBUG;ENABLE_TEST_CLOUD; 19 | prompt 20 | 4 21 | iPhone Developer 22 | true 23 | true 24 | true 25 | true 26 | true 27 | 56859 28 | None 29 | x86_64 30 | NSUrlSessionHandler 31 | x86 32 | true 33 | 34 | 35 | pdbonly 36 | true 37 | bin\iPhone\Release 38 | prompt 39 | 4 40 | iPhone Developer 41 | true 42 | Entitlements.plist 43 | SdkOnly 44 | ARM64 45 | NSUrlSessionHandler 46 | x86 47 | true 48 | true 49 | 50 | 51 | pdbonly 52 | true 53 | bin\iPhoneSimulator\Release 54 | prompt 55 | 4 56 | iPhone Developer 57 | true 58 | SdkOnly 59 | x86_64 60 | NSUrlSessionHandler 61 | x86 62 | true 63 | true 64 | true 65 | 66 | 67 | true 68 | full 69 | false 70 | bin\iPhone\Debug 71 | DEBUG;ENABLE_TEST_CLOUD; 72 | prompt 73 | 4 74 | iPhone Developer 75 | true 76 | true 77 | true 78 | true 79 | true 80 | true 81 | Entitlements.plist 82 | 64237 83 | SdkOnly 84 | ARM64 85 | NSUrlSessionHandler 86 | x86 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | {8B5DFD00-56B2-4D23-8548-900514AE289E} 132 | CosmosDbSampleApp 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/CustomRenderers/AddPersonPageCustomRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using UIKit; 4 | 5 | using Xamarin.Forms; 6 | using Xamarin.Forms.Platform.iOS; 7 | 8 | using CosmosDbSampleApp; 9 | using CosmosDbSampleApp.iOS; 10 | 11 | 12 | [assembly: ExportRenderer(typeof(AddPersonPage), typeof(AddPersonPageCustomRenderer))] 13 | namespace CosmosDbSampleApp.iOS 14 | { 15 | public class AddPersonPageCustomRenderer : PageRenderer 16 | { 17 | 18 | public override void ViewWillAppear(bool animated) 19 | { 20 | base.ViewWillAppear(animated); 21 | 22 | var thisElement = (AddPersonPage)Element; 23 | 24 | var leftNavList = new List(); 25 | var rightNavList = new List(); 26 | 27 | var navigationItem = NavigationController.TopViewController.NavigationItem; 28 | 29 | for (var i = 0; i < thisElement.ToolbarItems.Count; i++) 30 | { 31 | var reorder = (thisElement.ToolbarItems.Count - 1); 32 | var itemPriority = thisElement.ToolbarItems[reorder - i].Priority; 33 | 34 | if (itemPriority is 1) 35 | { 36 | var leftNavItems = navigationItem?.RightBarButtonItems?[i]; 37 | 38 | if (leftNavItems != null) 39 | leftNavList.Add(leftNavItems); 40 | } 41 | else if (itemPriority is 0) 42 | { 43 | var rightNavItems = navigationItem?.RightBarButtonItems?[i]; 44 | 45 | if (rightNavItems != null) 46 | rightNavList.Add(rightNavItems); 47 | } 48 | } 49 | 50 | if (navigationItem != null) 51 | { 52 | navigationItem.SetLeftBarButtonItems(leftNavList.ToArray(), false); 53 | navigationItem.SetRightBarButtonItems(rightNavList.ToArray(), false); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/CustomRenderers/EntryClearButtonCustomRenderer.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | using Xamarin.Forms; 4 | using Xamarin.Forms.Platform.iOS; 5 | 6 | using CosmosDbSampleApp.iOS; 7 | 8 | [assembly: ExportRenderer(typeof(Entry), typeof(EntryClearButtonCustomRenderer))] 9 | namespace CosmosDbSampleApp.iOS 10 | { 11 | public class EntryClearButtonCustomRenderer : EntryRenderer 12 | { 13 | protected override void OnElementChanged(ElementChangedEventArgs e) 14 | { 15 | base.OnElementChanged(e); 16 | 17 | if (Control == null) 18 | return; 19 | 20 | Control.ClearButtonMode = UITextFieldViewMode.WhileEditing; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | CosmosDbSampleApp 7 | CFBundleName 8 | CosmosDbSampleApp 9 | CFBundleIdentifier 10 | com.minnick.cosmosdbsampleapp 11 | CFBundleShortVersionString 12 | 1.0 13 | CFBundleVersion 14 | 1.0 15 | LSRequiresIPhoneOS 16 | 17 | MinimumOSVersion 18 | 8.0 19 | UIDeviceFamily 20 | 21 | 1 22 | 2 23 | 24 | UILaunchStoryboardName 25 | LaunchScreen 26 | UIRequiredDeviceCapabilities 27 | 28 | armv7 29 | 30 | UISupportedInterfaceOrientations 31 | 32 | UIInterfaceOrientationPortrait 33 | UIInterfaceOrientationLandscapeLeft 34 | UIInterfaceOrientationLandscapeRight 35 | 36 | UISupportedInterfaceOrientations~ipad 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationPortraitUpsideDown 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | XSAppIconAssets 44 | Assets.xcassets/AppIcon.appiconset 45 | UIViewControllerBasedStatusBarAppearance 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using UIKit; 2 | 3 | namespace CosmosDbSampleApp.iOS 4 | { 5 | public class Application 6 | { 7 | static void Main(string[] args) => UIApplication.Main(args, null, nameof(AppDelegate)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CosmosDbSampleApp.iOS/Resources/Add@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodeTraveler/CosmosDbSampleApp/c749c57183ead99993c4cecd6c08edd630ddb9d2/CosmosDbSampleApp.iOS/Resources/Add@2x.png -------------------------------------------------------------------------------- /CosmosDbSampleApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbSampleApp.iOS", "CosmosDbSampleApp.iOS\CosmosDbSampleApp.iOS.csproj", "{0D33938C-EB02-437F-A395-6BA569C7F6A1}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbSampleApp.Droid", "CosmosDbSampleApp.Droid\CosmosDbSampleApp.Droid.csproj", "{97D57FF9-B8E6-4C33-A10B-8A87063605F4}" 7 | EndProject 8 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CosmosDbSampleApp.Shared", "CosmosDbSampleApp.Shared\CosmosDbSampleApp.Shared.shproj", "{2E0B1C2E-14FE-42FF-9A68-1E7F9648D035}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbSampleApp.UITests", "CosmosDbSampleApp.UITests\CosmosDbSampleApp.UITests.csproj", "{DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosDbSampleApp", "CosmosDbSampleApp\CosmosDbSampleApp.csproj", "{8B5DFD00-56B2-4D23-8548-900514AE289E}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E530D3FE-B840-489F-9491-E11EF3710015}" 15 | ProjectSection(SolutionItems) = preProject 16 | Directory.Build.props = Directory.Build.props 17 | EndProjectSection 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 24 | Release|iPhone = Release|iPhone 25 | Release|iPhoneSimulator = Release|iPhoneSimulator 26 | Debug|iPhone = Debug|iPhone 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 30 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 31 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|Any CPU.ActiveCfg = Release|iPhone 32 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|Any CPU.Build.0 = Release|iPhone 33 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 34 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 35 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|iPhone.ActiveCfg = Release|iPhone 36 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|iPhone.Build.0 = Release|iPhone 37 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 38 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 39 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|iPhone.ActiveCfg = Debug|iPhone 40 | {0D33938C-EB02-437F-A395-6BA569C7F6A1}.Debug|iPhone.Build.0 = Debug|iPhone 41 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 46 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 47 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|iPhone.ActiveCfg = Release|Any CPU 48 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|iPhone.Build.0 = Release|Any CPU 49 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 50 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 51 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|iPhone.ActiveCfg = Debug|Any CPU 52 | {97D57FF9-B8E6-4C33-A10B-8A87063605F4}.Debug|iPhone.Build.0 = Debug|Any CPU 53 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 58 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 59 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|iPhone.ActiveCfg = Release|Any CPU 60 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|iPhone.Build.0 = Release|Any CPU 61 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 62 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 63 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|iPhone.ActiveCfg = Debug|Any CPU 64 | {DC1A1AC9-DD38-48F0-9EFD-E13A1B7901A6}.Debug|iPhone.Build.0 = Debug|Any CPU 65 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 70 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 71 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|iPhone.ActiveCfg = Release|Any CPU 72 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|iPhone.Build.0 = Release|Any CPU 73 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 74 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 75 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|iPhone.ActiveCfg = Debug|Any CPU 76 | {8B5DFD00-56B2-4D23-8548-900514AE289E}.Debug|iPhone.Build.0 = Debug|Any CPU 77 | EndGlobalSection 78 | EndGlobal 79 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/App.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace CosmosDbSampleApp 4 | { 5 | public class App : Application 6 | { 7 | public App() => MainPage = new NavigationPage(new PersonListPage()) 8 | { 9 | BarBackgroundColor = ColorConstants.BarBackgroundColor, 10 | BarTextColor = ColorConstants.BarTextColor 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Constants/ColorConstants.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace CosmosDbSampleApp 4 | { 5 | public static class ColorConstants 6 | { 7 | public static readonly Color BarBackgroundColor = Color.FromHex("F1B340"); 8 | public static readonly Color BarTextColor = Color.White; 9 | public static readonly Color PageBackgroundColor = Color.FromHex("F8E28B"); 10 | public static readonly Color EntryBackgroundColor = Color.FromHex("FFF8DD"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Constants/DocumentDbConstants.cs: -------------------------------------------------------------------------------- 1 | namespace CosmosDbSampleApp 2 | { 3 | public static class DocumentDbConstants 4 | { 5 | #error Missing Keys 6 | //Create a new Azure Cosmos DB to generate a new Read/Write Primary Key and new Read Only Primary Key 7 | public const string ReadWritePrimaryKey = "Add Read Write Primary Key"; 8 | public const string ReadOnlyPrimaryKey = "Add Read Primary Key"; 9 | 10 | public const string Url = "https://cosmoshack.documents.azure.com:443/"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/CosmosDbSampleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Models/CosmosDbModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace CosmosDbSampleApp 5 | { 6 | public abstract class CosmosDbModel where T : CosmosDbModel 7 | { 8 | public static string CollectionId => typeof(T).Name; 9 | public static string DatabaseId => "CosmosDbSampleAppDatabase"; 10 | 11 | public string TypeName => typeof(T).Name; 12 | 13 | [JsonProperty("id")] 14 | public string Id { get; init; } = Guid.NewGuid().ToString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Models/PersonModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace CosmosDbSampleApp 4 | { 5 | public class PersonModel : CosmosDbModel 6 | { 7 | public string Name { get; init; } = string.Empty; 8 | public int Age { get; init; } 9 | } 10 | } 11 | 12 | namespace System.Runtime.CompilerServices 13 | { 14 | [EditorBrowsable(EditorBrowsableState.Never)] 15 | public record IsExternalInit; 16 | } -------------------------------------------------------------------------------- /CosmosDbSampleApp/Page/AddPersonPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CosmosDbSampleApp.Shared; 4 | using Xamarin.Forms; 5 | using Xamarin.CommunityToolkit.Markup; 6 | using Xamarin.Essentials; 7 | 8 | namespace CosmosDbSampleApp 9 | { 10 | public class AddPersonPage : BaseContentPage 11 | { 12 | const string _saveButtonToolBarItemText = "Save"; 13 | const string _cancelButtonToolBarItemText = "Cancel"; 14 | 15 | readonly AddPersonPageEntry _nameEntry; 16 | readonly ActivityIndicator _activityIndicator; 17 | 18 | public AddPersonPage() 19 | { 20 | ViewModel.SaveErrored += HandleError; 21 | ViewModel.SaveCompleted += HandleSaveCompleted; 22 | 23 | ToolbarItems.Add(new ToolbarItem 24 | { 25 | Text = _saveButtonToolBarItemText, 26 | Priority = 0, 27 | AutomationId = AutomationIdConstants.AddPersonPage_SaveButton 28 | }.Bind(ToolbarItem.CommandProperty, nameof(AddPersonViewModel.SaveButtonCommand))); 29 | 30 | ToolbarItems.Add(new ToolbarItem 31 | { 32 | Text = _cancelButtonToolBarItemText, 33 | Priority = 1, 34 | AutomationId = AutomationIdConstants.AddPersonPage_CancelButton 35 | }.Invoke(cancelButton => cancelButton.Clicked += HandleCancelButtonToolbarItemClicked)); 36 | 37 | Padding = new Thickness(20, 20, 20, 0); 38 | 39 | Title = PageTitles.AddPersonPage; 40 | 41 | Content = new ScrollView 42 | { 43 | Content = new StackLayout 44 | { 45 | Children = 46 | { 47 | new Label { Text = "Name" }, 48 | 49 | new AddPersonPageEntry 50 | { 51 | Placeholder = "Name", 52 | AutomationId = AutomationIdConstants.AddPersonPage_NameEntry, 53 | ReturnType = ReturnType.Next 54 | }.Bind(Entry.TextProperty, nameof(AddPersonViewModel.NameEntryText)) 55 | .Bind(IsEnabledProperty, nameof(AddPersonViewModel.IsBusy), convert: isBusy => !isBusy) 56 | .Assign(out _nameEntry), 57 | 58 | new Label { Text = "Age" }, 59 | 60 | new AddPersonPageEntry 61 | { 62 | Placeholder = "Age", 63 | Keyboard = Keyboard.Numeric, 64 | AutomationId = AutomationIdConstants.AddPersonPage_AgeEntry, 65 | ReturnType = ReturnType.Go, 66 | }.Bind(Entry.TextProperty, nameof(AddPersonViewModel.AgeEntryText)) 67 | .Bind(Entry.ReturnCommandProperty, nameof(AddPersonViewModel.SaveButtonCommand)) 68 | .Bind(IsEnabledProperty, nameof(AddPersonViewModel.IsBusy), convert: isBusy => !isBusy), 69 | 70 | new ActivityIndicator 71 | { 72 | AutomationId = AutomationIdConstants.AddPersonPage_ActivityIndicator 73 | }.Bind(IsVisibleProperty, nameof(AddPersonViewModel.IsBusy)) 74 | .Bind(ActivityIndicator.IsRunningProperty, nameof(AddPersonViewModel.IsBusy)) 75 | .Assign(out _activityIndicator) 76 | } 77 | } 78 | }; 79 | } 80 | 81 | protected override void OnAppearing() 82 | { 83 | base.OnAppearing(); 84 | 85 | MainThread.BeginInvokeOnMainThread(() => _nameEntry.Focus()); 86 | } 87 | 88 | async void HandleCancelButtonToolbarItemClicked(object sender, EventArgs e) 89 | { 90 | if (_activityIndicator.IsRunning) 91 | return; 92 | 93 | await PopPageFromNavigationStack(); 94 | } 95 | 96 | void HandleError(object sender, string message) 97 | { 98 | MainThread.BeginInvokeOnMainThread(async () => await DisplayAlert("Error", message, "ok")); 99 | } 100 | 101 | async void HandleSaveCompleted(object sender, EventArgs e) => await PopPageFromNavigationStack(); 102 | 103 | Task PopPageFromNavigationStack() => MainThread.InvokeOnMainThreadAsync(Navigation.PopModalAsync); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Page/Base/BaseContentPage.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace CosmosDbSampleApp 4 | { 5 | public abstract class BaseContentPage : ContentPage where T : BaseViewModel, new() 6 | { 7 | protected BaseContentPage() 8 | { 9 | BindingContext = ViewModel; 10 | BackgroundColor = Color.FromHex("F8E28B"); 11 | } 12 | 13 | protected T ViewModel { get; } = new T(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Page/PersonListPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CosmosDbSampleApp.Shared; 4 | using Xamarin.CommunityToolkit.Markup; 5 | using Xamarin.Essentials; 6 | using Xamarin.Forms; 7 | using static Xamarin.CommunityToolkit.Markup.GridRowsColumns; 8 | 9 | namespace CosmosDbSampleApp 10 | { 11 | class PersonListPage : BaseContentPage 12 | { 13 | public PersonListPage() 14 | { 15 | Title = PageTitles.PersonListPage; 16 | ViewModel.ErrorTriggered += HandleErrorTriggered; 17 | 18 | ToolbarItems.Add(new ToolbarItem 19 | { 20 | IconImageSource = "Add", 21 | AutomationId = AutomationIdConstants.PersonListPage_AddButton 22 | }.Invoke(addButtonToolBarItem => addButtonToolBarItem.Clicked += HandleAddButtonClicked)); 23 | 24 | Content = new Grid 25 | { 26 | RowDefinitions = Rows.Define(Star), 27 | ColumnDefinitions = Columns.Define(Star), 28 | 29 | Children = 30 | { 31 | new RefreshView 32 | { 33 | RefreshColor = Color.Black, 34 | Content = new CollectionView 35 | { 36 | ItemTemplate = new PersonListDataTemplate(), 37 | BackgroundColor = ColorConstants.PageBackgroundColor, 38 | AutomationId = AutomationIdConstants.PersonListPage_PersonList 39 | }.Bind(CollectionView.ItemsSourceProperty, nameof(PersonListViewModel.PersonList)) 40 | .Invoke(personList => personList.SelectionChanged += HandleSelectionChanged) 41 | 42 | }.Bind(RefreshView.IsRefreshingProperty, nameof(PersonListViewModel.IsRefreshing)) 43 | .Bind(RefreshView.CommandProperty, nameof(PersonListViewModel.PullToRefreshCommand)), 44 | 45 | new BoxView { BackgroundColor = new Color(1, 1, 1, 0.75) }.CenterExpand() 46 | .Bind(IsVisibleProperty, nameof(PersonListViewModel.IsDeletingPerson)), 47 | 48 | new ActivityIndicator { AutomationId = AutomationIdConstants.PersonListPage_ActivityIndicator }.Center() 49 | .Bind(IsVisibleProperty, nameof(PersonListViewModel.IsDeletingPerson)) 50 | .Bind(ActivityIndicator.IsRunningProperty, nameof(PersonListViewModel.IsDeletingPerson)) 51 | } 52 | }; 53 | } 54 | 55 | protected override void OnAppearing() 56 | { 57 | base.OnAppearing(); 58 | 59 | if (Content is Layout layout 60 | && layout.Children.OfType().First() is RefreshView refreshView) 61 | { 62 | refreshView.IsRefreshing = true; 63 | } 64 | } 65 | 66 | void HandleErrorTriggered(object sender, string e) => 67 | MainThread.BeginInvokeOnMainThread(async () => await DisplayAlert("Error", e, "OK ")); 68 | 69 | 70 | void HandleSelectionChanged(object sender, SelectionChangedEventArgs e) 71 | { 72 | var listView = (CollectionView)sender; 73 | listView.SelectedItem = null; 74 | } 75 | 76 | void HandleAddButtonClicked(object sender, EventArgs e) 77 | { 78 | MainThread.BeginInvokeOnMainThread(async () => 79 | { 80 | await Navigation.PushModalAsync(new NavigationPage(new AddPersonPage()) 81 | { 82 | BarTextColor = ColorConstants.BarTextColor, 83 | BarBackgroundColor = ColorConstants.BarBackgroundColor 84 | }); 85 | }); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Services/DebugService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace CosmosDbSampleApp 6 | { 7 | public static class DebugService 8 | { 9 | public static void PrintException( 10 | Exception exception, 11 | [CallerMemberName] string callerMemberName = "", 12 | [CallerLineNumber] int lineNumber = 0, 13 | [CallerFilePath] string filePath = "") 14 | { 15 | var fileName = System.IO.Path.GetFileName(filePath); 16 | 17 | Debug.WriteLine(exception.GetType()); 18 | Debug.WriteLine($"Error: {exception.Message}"); 19 | Debug.WriteLine($"Line Number: {lineNumber}"); 20 | Debug.WriteLine($"Caller Name: {callerMemberName}"); 21 | Debug.WriteLine($"File Name: {fileName}"); 22 | Debug.WriteLine(exception); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Services/DocumentDbService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Collections.Generic; 6 | using Xamarin.Forms; 7 | using Microsoft.Azure.Cosmos; 8 | using Microsoft.Azure.Cosmos.Linq; 9 | using Xamarin.Essentials; 10 | 11 | namespace CosmosDbSampleApp 12 | { 13 | public static class DocumentDbService 14 | { 15 | static readonly Container _readOnlyContainer = new CosmosClient(DocumentDbConstants.Url, DocumentDbConstants.ReadOnlyPrimaryKey).GetContainer(PersonModel.DatabaseId, PersonModel.CollectionId); 16 | static readonly Container _readWriteContainer = DocumentDbConstants.ReadWritePrimaryKey == "Add Read Write Primary Key" 17 | ? throw new DocumentDbException("Invalid ReadWrite Primary Key") 18 | : new CosmosClient(DocumentDbConstants.Url, DocumentDbConstants.ReadWritePrimaryKey).GetContainer(PersonModel.DatabaseId, PersonModel.CollectionId); 19 | 20 | static int _networkIndicatorCount; 21 | 22 | public static async IAsyncEnumerable GetAll() where T : CosmosDbModel 23 | { 24 | await SetActivityIndicatorStatus(true).ConfigureAwait(false); 25 | 26 | try 27 | { 28 | 29 | var queryable = _readOnlyContainer.GetItemLinqQueryable().Where(x => x.TypeName.Equals(typeof(T).Name)); 30 | var feedIterator = queryable.ToFeedIterator(); 31 | 32 | if (feedIterator.HasMoreResults) 33 | { 34 | var responseList = await feedIterator.ReadNextAsync().ConfigureAwait(false); 35 | 36 | foreach (var response in responseList) 37 | yield return response; 38 | } 39 | } 40 | finally 41 | { 42 | await SetActivityIndicatorStatus(false).ConfigureAwait(false); 43 | } 44 | } 45 | 46 | public static async Task> UpsertItem(T document) where T : CosmosDbModel 47 | { 48 | await SetActivityIndicatorStatus(true).ConfigureAwait(false); 49 | 50 | try 51 | { 52 | return await _readWriteContainer.UpsertItemAsync(document).ConfigureAwait(false); 53 | } 54 | finally 55 | { 56 | await SetActivityIndicatorStatus(false).ConfigureAwait(false); 57 | } 58 | } 59 | 60 | public static async Task Delete(string id) where T : CosmosDbModel 61 | { 62 | await SetActivityIndicatorStatus(true).ConfigureAwait(false); 63 | 64 | try 65 | { 66 | var result = await _readWriteContainer.DeleteItemAsync(id, PartitionKey.Null); 67 | 68 | if (!IsSuccessStatusCode(result.StatusCode)) 69 | throw new Exception($"Delete Failed: {result.StatusCode}"); 70 | } 71 | finally 72 | { 73 | await SetActivityIndicatorStatus(false).ConfigureAwait(false); 74 | } 75 | } 76 | 77 | static async Task SetActivityIndicatorStatus(bool isNetworkConnectionActive) 78 | { 79 | if (isNetworkConnectionActive) 80 | { 81 | _networkIndicatorCount++; 82 | await MainThread.InvokeOnMainThreadAsync(() => Application.Current.MainPage.IsBusy = true).ConfigureAwait(false); 83 | } 84 | else if (--_networkIndicatorCount <= 0) 85 | { 86 | _networkIndicatorCount = 0; 87 | await MainThread.InvokeOnMainThreadAsync(() => Application.Current.MainPage.IsBusy = false).ConfigureAwait(false); 88 | } 89 | } 90 | 91 | static bool IsSuccessStatusCode(in HttpStatusCode statusCode) => (int)statusCode >= 200 && (int)statusCode <= 299; 92 | 93 | class DocumentDbException : Exception 94 | { 95 | public DocumentDbException(in string message) : base(message) 96 | { 97 | 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/ViewModels/AddPersonViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using System.Threading.Tasks; 4 | using AsyncAwaitBestPractices; 5 | using AsyncAwaitBestPractices.MVVM; 6 | 7 | namespace CosmosDbSampleApp 8 | { 9 | public class AddPersonViewModel : BaseViewModel 10 | { 11 | readonly WeakEventManager _saveCompletedEventManager = new(); 12 | readonly WeakEventManager _saveErroredEventManager = new(); 13 | 14 | bool _isBusy; 15 | 16 | string _ageEntryText = string.Empty, 17 | _nameEntryText = string.Empty; 18 | 19 | public AddPersonViewModel() 20 | { 21 | SaveButtonCommand = new AsyncCommand(() => ExecuteSaveButtonCommand(AgeEntryText, NameEntryText)); 22 | } 23 | 24 | public event EventHandler SaveErrored 25 | { 26 | add => _saveErroredEventManager.AddEventHandler(value); 27 | remove => _saveErroredEventManager.RemoveEventHandler(value); 28 | } 29 | 30 | public event EventHandler SaveCompleted 31 | { 32 | add => _saveCompletedEventManager.AddEventHandler(value); 33 | remove => _saveCompletedEventManager.RemoveEventHandler(value); 34 | } 35 | 36 | public ICommand SaveButtonCommand { get; } 37 | 38 | public string AgeEntryText 39 | { 40 | get => _ageEntryText; 41 | set => SetProperty(ref _ageEntryText, value); 42 | } 43 | 44 | public string NameEntryText 45 | { 46 | get => _nameEntryText; 47 | set => SetProperty(ref _nameEntryText, value); 48 | } 49 | 50 | public bool IsBusy 51 | { 52 | get => _isBusy; 53 | set => SetProperty(ref _isBusy, value); 54 | } 55 | 56 | async Task ExecuteSaveButtonCommand(string ageEntryText, string nameEntryText) 57 | { 58 | var ageParseSucceeded = int.TryParse(ageEntryText, out var age); 59 | if (!ageParseSucceeded) 60 | { 61 | OnSaveErrorred("Age Must Be A Whole Number"); 62 | return; 63 | } 64 | 65 | var person = new PersonModel 66 | { 67 | Name = nameEntryText, 68 | Age = age 69 | }; 70 | 71 | IsBusy = true; 72 | 73 | try 74 | { 75 | var result = await DocumentDbService.UpsertItem(person).ConfigureAwait(false); 76 | 77 | if (result is null) 78 | OnSaveErrorred("Save Failed"); 79 | else 80 | OnSaveCompleted(); 81 | } 82 | catch (Exception e) 83 | { 84 | OnSaveErrorred(e.Message); 85 | DebugService.PrintException(e); 86 | } 87 | finally 88 | { 89 | IsBusy = false; 90 | } 91 | } 92 | 93 | void OnSaveCompleted() => _saveCompletedEventManager.RaiseEvent(this, EventArgs.Empty, nameof(SaveCompleted)); 94 | void OnSaveErrorred(in string message) => _saveErroredEventManager.RaiseEvent(this, message, nameof(SaveErrored)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/ViewModels/Base/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using AsyncAwaitBestPractices; 6 | 7 | namespace CosmosDbSampleApp 8 | { 9 | public class BaseViewModel : INotifyPropertyChanged 10 | { 11 | readonly WeakEventManager _notifyPropertyChangedEventManager = new WeakEventManager(); 12 | 13 | event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged 14 | { 15 | add => _notifyPropertyChangedEventManager.AddEventHandler(value); 16 | remove => _notifyPropertyChangedEventManager.RemoveEventHandler(value); 17 | } 18 | 19 | protected void SetProperty(ref T backingStore, in T value, in Action? onChanged = null, [CallerMemberName] in string propertyname = "") 20 | { 21 | if (EqualityComparer.Default.Equals(backingStore, value)) 22 | return; 23 | 24 | backingStore = value; 25 | 26 | onChanged?.Invoke(); 27 | 28 | OnPropertyChanged(propertyname); 29 | } 30 | 31 | void OnPropertyChanged([CallerMemberName] in string name = "") => 32 | _notifyPropertyChangedEventManager.RaiseEvent(this, new PropertyChangedEventArgs(name), nameof(INotifyPropertyChanged.PropertyChanged)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/ViewModels/PersonListViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.ObjectModel; 4 | using System.Threading.Tasks; 5 | using System.Windows.Input; 6 | using AsyncAwaitBestPractices; 7 | using AsyncAwaitBestPractices.MVVM; 8 | 9 | namespace CosmosDbSampleApp 10 | { 11 | class PersonListViewModel : BaseViewModel 12 | { 13 | readonly WeakEventManager _errorTriggeredEventManager = new(); 14 | 15 | bool _isDeletingPerson, _isRefreshing; 16 | 17 | public PersonListViewModel() 18 | { 19 | //Ensure Observable Collection is Thread Safe https://codetraveler.io/2019/09/11/using-observablecollection-in-a-multi-threaded-xamarin-forms-application/ 20 | Xamarin.Forms.BindingBase.EnableCollectionSynchronization(PersonList, null, ObservableCollectionCallback); 21 | 22 | PullToRefreshCommand = new AsyncCommand(UpdatePersonList); 23 | } 24 | 25 | public event EventHandler ErrorTriggered 26 | { 27 | add => _errorTriggeredEventManager.AddEventHandler(value); 28 | remove => _errorTriggeredEventManager.RemoveEventHandler(value); 29 | } 30 | 31 | public ICommand PullToRefreshCommand { get; } 32 | 33 | public ObservableCollection PersonList { get; } = new(); 34 | 35 | public bool IsDeletingPerson 36 | { 37 | get => _isDeletingPerson; 38 | set => SetProperty(ref _isDeletingPerson, value); 39 | } 40 | 41 | public bool IsRefreshing 42 | { 43 | get => _isRefreshing; 44 | set => SetProperty(ref _isRefreshing, value); 45 | } 46 | 47 | async Task UpdatePersonList() 48 | { 49 | PersonList.Clear(); 50 | 51 | try 52 | { 53 | await foreach (var personModel in DocumentDbService.GetAll()) 54 | { 55 | PersonList.Add(personModel); 56 | } 57 | } 58 | catch (Exception e) 59 | { 60 | OnErrorTriggered(e.InnerException is null ? e.Message : e.InnerException.Message); 61 | DebugService.PrintException(e); 62 | } 63 | finally 64 | { 65 | IsRefreshing = false; 66 | } 67 | } 68 | 69 | //Ensure Observable Collection is Thread Safe https://codetraveler.io/2019/09/11/using-observablecollection-in-a-multi-threaded-xamarin-forms-application/ 70 | void ObservableCollectionCallback(IEnumerable collection, object context, Action? accessMethod, bool writeAccess) 71 | { 72 | lock (collection) 73 | { 74 | accessMethod?.Invoke(); 75 | } 76 | } 77 | 78 | void OnErrorTriggered(in string message) => _errorTriggeredEventManager.RaiseEvent(this, message, nameof(ErrorTriggered)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Views/AddPerson/AddPersonPageEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace CosmosDbSampleApp 5 | { 6 | class AddPersonPageEntry : Entry 7 | { 8 | public AddPersonPageEntry() 9 | { 10 | ClearButtonVisibility = ClearButtonVisibility.WhileEditing; 11 | 12 | switch (Device.RuntimePlatform) 13 | { 14 | case Device.iOS: 15 | BackgroundColor = ColorConstants.EntryBackgroundColor; 16 | Margin = new Thickness(0, 0, 0, 5); 17 | break; 18 | 19 | case Device.Android: 20 | Margin = new Thickness(0, 0, 0, 10); 21 | break; 22 | 23 | default: 24 | throw new NotSupportedException(); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CosmosDbSampleApp/Views/PersonList/PersonListDataTemplate.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.CommunityToolkit.Markup; 2 | using Xamarin.Forms; 3 | using static Xamarin.CommunityToolkit.Markup.GridRowsColumns; 4 | 5 | namespace CosmosDbSampleApp 6 | { 7 | class PersonListDataTemplate : DataTemplate 8 | { 9 | public PersonListDataTemplate() : base(CreateDataTemplate) 10 | { 11 | } 12 | 13 | static Grid CreateDataTemplate() => new() 14 | { 15 | RowSpacing = 1, 16 | 17 | RowDefinitions = Rows.Define( 18 | (Row.Name, 20), 19 | (Row.Age, 20), 20 | (Row.Separator, 1)), 21 | 22 | Children = 23 | { 24 | new BlackLabel(16).CenterVertical().Font(bold: true) 25 | .Row(Row.Name) 26 | .Bind(Label.TextProperty, nameof(PersonModel.Name)), 27 | 28 | new BlackLabel(13).Font(italic: true) 29 | .Row(Row.Age) 30 | .Bind(Label.TextProperty, nameof(PersonModel.Age)) 31 | .Invoke(detailLabel => detailLabel.Padding = new Thickness(detailLabel.Padding.Left, detailLabel.Padding.Top, detailLabel.Padding.Right, detailLabel.Padding.Bottom + 5)), 32 | 33 | new BoxView { Color = Color.DarkGray }.Margin(5, 0) 34 | .Row(Row.Separator) 35 | } 36 | }; 37 | 38 | enum Row { Name, Age, Separator } 39 | 40 | class BlackLabel : Label 41 | { 42 | public BlackLabel(double fontSize) 43 | { 44 | Padding = new Thickness(10, 0); 45 | FontSize = fontSize; 46 | TextColor = Color.Black; 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | latest 5 | enable 6 | nullable 7 | True 8 | false 9 | 10 | 11 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Brandon Minnick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Xamarin](https://github.com/brminnick/CosmosDbSampleApp/actions/workflows/mobile.yml/badge.svg)](https://github.com/brminnick/CosmosDbSampleApp/actions/workflows/mobile.yml) 2 | 3 | # CosmosDbSampleApp 4 | 5 | A Xamarin.iOS and Xamarin.Android app built using Xamarin.Forms that utilizes a [Cosmos DB Backend](https://docs.microsoft.com/azure/cosmos-db/mobile-apps-with-xamarin?WT.mc_id=mobile-0000-bramin). 6 | 7 | It was also was featured on [The Xamarin Show, Scalable + Secure Data with CosmosDB for Mobile](https://channel9.msdn.com/Shows/XamarinShow/Scalable--Service-Data-with-CosmosDB-for-Mobile?WT.mc_id=mobile-0000-bramin). 8 | 9 | [![The Xamarin Show](https://user-images.githubusercontent.com/13558917/57717092-4b471180-762f-11e9-9ee7-a5e9b66bb389.png)](https://channel9.msdn.com/Shows/XamarinShow/Scalable--Service-Data-with-CosmosDB-for-Mobile?WT.mc_id=mobile-0000-bramin) 10 | --------------------------------------------------------------------------------